C# 14 新功能 Extension Members (1)

如果要在 C# 14 的眾多新功能中挑選一個最令我心動的,一定是 Extension members。這項功能不僅是對既有  this extension methods 的延展,而且是進一步的語言層級提升、更為貼近原生成員的使用體驗。

過去與未來

過去的 extension methods 雖然解決了「在不修改原始程式碼的情況下擴充型別」的需求,但它始終帶有一種「外掛」的感覺;語法上獨立、語意上稍顯疏離。而 Extension members 試圖徹底改變這種隔閡。它讓我們能在介面或結構中定義擴充成員,並且以一致的方式被呼叫,感覺上就像是型別本身的原生能力。這使得 API 設計能展現優雅的協調性,也讓程式碼的可讀性與可維護性大幅改善。
更重要的是,Extension members 為物件導向與擴充性之間搭起了一座橋樑。它讓開發者能在保持封裝性的同時,提供更靈活的擴充方案。對於大型系統或框架設計而言,這代表著更乾淨的架構、更優雅的程式碼,以及更少的技術債。
若用形而上學的方式看待,Extension members 不只是語法糖,而是 C# 語言邁向成熟與一致性的關鍵一步,讓我們能以更自然的方式表達程式設計的意圖,這正是我認為 C# 14 最令人期待的進化。

更廣泛的擴充

過去的 this 擴充方法主要著重於模擬執行個體方法的擴充 (也就是偽裝成執行體方法), 而 Extension members 能夠延伸出更為強大的擴充能力,包含:

  1. 執行個體方法 – 延伸型別的行為邏輯
  2. 執行個體屬性 – 提供額外的狀態存取
  3. 靜態方法 – 擴充型別的工具性功能
  4. 靜態屬性 – 增添型別層級的共享資訊
基本形式的差異 – 執行個體方法的擴充

C#開發者普遍熟悉設計傳統的 this 擴充方法,採用這方式需要先設計一個靜態類別並在其中定義靜態方法。該方法的第一個參數加上 this  關鍵字,表明此參數的型別是接收器 (receiver),使得該方法能被視為該型別的執行個體方法;這種設計雖然便利,卻侷限於模擬執行個體方法的擴充。

例如一個原有的矩形類別,在原有的程式碼中只具有 寬與高兩個屬性以及取得面積的 GetArea 方法:

public class MyRectangle
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double GetArea()
        => Width * Height;       
}

採用 this 擴充方法形式擴充一個取得對角線的方法如下:

 public static class MyRectangleExtensions
 {
     public static double GetDiagonal(this MyRectangle rect)
     {
         if (rect is null) { throw new NullReferenceException(); }
         return Math.Sqrt(rect.Width * rect.Width + rect.Height * rect.Height);
     }
 }

採用 C# 14 Extension members 的方式會在靜態類別內部定義一個 extension 區塊,同時宣告接收器 (receiver) 型別:

 public static class MyRectangleExtensions
 {
     extension(MyRectangle rect)
     {        
         public double GetDiagonal()
         {
             if (rect is null) { throw new NullReferenceException(); }
             return Math.Sqrt(rect.Width * rect.Width + rect.Height * rect.Height);
         }
     }
 }

這種 extension 區塊的最大優勢在於 一致的接收器宣告。在區塊開頭就明確指定接收器型別,之後區塊內的所有方法與屬性都共享同一個接收器,語意與原有執行個體完全一致,包含無需 static 宣告與原生方法相同的參數列表內容,如此的設計帶來幾個好處:

  1. 語法更直觀 – 不需要在每個方法的第一個參數重複加上 this ,區塊本身就已經宣告了接收器,讓程式碼更簡潔。
  2. 語意更一致 – 區塊內的方法與屬性看起來就像是型別的原生成員,設計與呼叫方式與原有執行個體完全相同,減少了「外掛」的感覺。
  3. 可維護性提升 – 當需要為某個型別擴充多個功能時,所有擴充成員集中在同一個 extension 區塊中,結構更清晰,方便維護與閱讀。
  4. 設計更優雅 – Extension members 不僅能擴充執行個體方法,還能擴充屬性與靜態成員,讓 API 設計更具協調性,程式架構更乾淨。
執行個體屬性的擴充

延續矩形的例子,這裡可以展示 Extension members 擴充執行個體屬性 的優點。除了方法之外,Extension 區塊允許我們直接定義屬性,語法與原生屬性一致,呼叫方式也完全相同。以「周長 (Perimeter)」為例:

 public static class MyRectangleExtensions
 {

     extension(MyRectangle rect)
     {
         /// <summary>
         /// 根據矩形的寬度與高度計算對角線的長度 (擴充為方法)
         /// </summary>
         /// <returns>回傳對角線的長度,型別為雙精度浮點數 (double)</returns>
         public double GetDiagonal()
         {
             if (rect is null) { throw new NullReferenceException(); }
             return Math.Sqrt(rect.Width * rect.Width + rect.Height * rect.Height);
         }

         /// <summary>
         ///  根據矩形的寬度與高度計算周長 (擴充為屬性)
         ///  擴充屬性無法存取私有欄位,也不能使用 field 關鍵字
         /// </summary>
         public double Perimeter
         {
             get
             {
                 if (rect is null) { throw new NullReferenceException(); }
                 return (rect.Width + rect.Height) * 2;
             }
         }
     }
 }

因為是『擴充』,想當然耳是不能直接存取原有 MyRectangle 型別內任何的私有成員,field 關鍵字也是不能使用的 (請參閱前文 C# 14 新功能 feild backed properties)。

擴充靜態方法與屬性

靜態成員的擴充,讓『擴充』的意義從「物件層級」延伸到 「型別層級」,靜態方法的傳統擴充設計方式,往往被宣告於獨立的工具類別 (utility class) 中,僅作為輔助。雖然能解決需求,但語意上與被擴充型別本身存在距離,開發者必須額外記住工具類別名稱,程式架構也容易分散;設計在工具類別中的靜態屬性更難以與被擴充型別本身建立語意上的關聯。

舉一個我們常用的 Enumerable.Range 為例,這是一個用來產生連續整數序列的方法,現在我們都用慣了,感覺沒什麼問題;但換個角度思考,如果 Range  這方法是設計在 int 型別上,使用起來是不是更直覺呢?
假設 Range 被設計在自訂的 utility class 的靜態方法中,使用者往往難以記住它隸屬的類別,語意上也與型別本身脫節。相反地,透過 Extension members 將 Range 直接擴充到 int 型別,就能以 int.Range  的方式呼叫,語法與語意都更自然,讓功能真正融入型別本身——這正是 Extension members 擴充靜態方法的重要意義。

靜態屬性也是一樣的道理,透過 Extension members 所擴充的靜態屬性能表達與型別本身強烈的關聯。傳統上,若將靜態屬性設計在工具類別中,語意上往往顯得疏離,使用者也難以直覺地聯想到原型別。此時通常會以泛型的方式呈現,像是 EqualityComparer<int>.Default 這種設計方式。
這種設計雖然能解決需求,但語意上仍顯得間接,因為使用者必須先想到工具類別,再指定型別,才能取得預設值。若能透過 Extension members 直接在型別上擴充靜態屬性,例如 ,就能讓語意更直覺,呼叫方式也更自然。這樣的設計不僅讓功能真正融入型別本身,也讓 API 設計展現更高的一致性與協調性。

透過 Extension members 擴充 Range  與 EqualityComparer  ,我們能在呼叫端以更直覺的方式表達設計意圖:

  public static class MyExtensions
  {
      extension(int source)
      {
          public static IEnumerable<int> Range(int start, int count)
          {
              return Enumerable.Range(start, count);
          }

          public static IEqualityComparer<int> EqualityComparer => EqualityComparer<int>.Default;
      }
  }
 var result = int.Range(5, 10);   
 var comparer = int.EqualityComparer;

相較於傳統的 Enumerable.Range 或 EqualityComparer<int>.Default ,這樣的呼叫方式更貼近型別本身的語意,讓功能真正融入型別之中。