擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種特殊的靜態方法,但可以像擴展類型上的實例方法一樣進行調用。 對於用 C# 和 Visual Basic 編寫的客戶端代碼,調用擴展方法與調用在類型中實際定義的方法之間沒有明顯的差異。
最常見的擴展方法是 LINQ 標准查詢運算符,它將查詢功能添加到現有的 System.Collections.IEnumerable 和 System.Collections.Generic.IEnumerable<T> 類型。 若要使用標准查詢運算符,請先使用 using System.Linq 指令將它們置於范圍中。 然后,任何實現了 IEnumerable<T> 的類型看起來都具有 GroupBy、OrderBy、Average 等實例方法。 在 IEnumerable<T>類型的實例(如 List<T> 或 Array)后鍵入“dot”時,可以在 IntelliSense 語句完成中看到這些附加方法。
下面的示例演示如何對一個整數數組調用標准查詢運算符 OrderBy 方法。 括號里面的表達式是一個 lambda 表達式。 很多標准查詢運算符采用 lambda 表達式作為參數,但這不是擴展方法的必要條件。 有關詳細信息,請參閱 Lambda 表達式(C# 編程指南)。
class ExtensionMethods2 { static void Main() { int[] ints = { 10, 45, 15, 39, 21, 26 }; var result = ints.OrderBy(g => g); foreach (var i in result) { System.Console.Write(i + " "); } } } //Output: 10 15 21 26 39 45
擴展方法被定義為靜態方法,但它們是通過實例方法語法進行調用的。 它們的第一個參數指定該方法作用於哪個類型,並且該參數以 this 修飾符為前綴。 僅當你使用 using 指令將命名空間顯式導入到源代碼中之后,擴展方法才位於范圍中。
下面的示例演示為 System.String 類定義的一個擴展方法。 請注意,它是在非嵌套的、非泛型靜態類內部定義的:
namespace ExtensionMethods { public static class MyExtensions { public static int WordCount(this String str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } }
可使用此 using 指令將 WordCount 擴展方法置於范圍中:
using ExtensionMethods;
而且,可以使用以下語法從應用程序中調用該擴展方法:
string s = "Hello Extension Methods"; int i = s.WordCount();
在代碼中,可以使用實例方法語法調用該擴展方法。 但是,編譯器生成的中間語言 (IL) 會將代碼轉換為對靜態方法的調用。 因此,並未真正違反封裝原則。 實際上,擴展方法無法訪問它們所擴展的類型中的私有變量。
有關詳細信息,請參閱如何:實現和調用自定義擴展方法(C# 編程指南)。
通常,你更多時候是調用擴展方法而不是實現你自己的擴展方法。 由於擴展方法是使用實例方法語法調用的,因此不需要任何特殊知識即可從客戶端代碼中使用它們。 若要為特定類型啟用擴展方法,只需為在其中定義這些方法的命名空間添加 using 指令。 例如,若要使用標准查詢運算符,請將此 using 指令添加到代碼中:
using System.Linq;
(你可能還必須添加對 System.Core.dll 的引用。)你將注意到,標准查詢運算符現在作為可供大多數 IEnumerable<T> 類型使用的附加方法顯示在 IntelliSense 中。
![]() |
---|
盡管標准查詢運算符沒有顯示在 String 的 IntelliSense 中,但它們仍然可用。 |
下面的示例演示 C# 編譯器在確定是將方法調用綁定到類型上的實例方法還是綁定到擴展方法時所遵循的規則。 靜態類 Extensions 包含為任何實現了 IMyInterface 的類型定義的擴展方法。 類 A、B 和 C 都實現了該接口。
MethodB 擴展方法永遠不會被調用,因為它的名稱和簽名與這些類已經實現的方法完全匹配。
如果編譯器找不到具有匹配簽名的實例方法,它會綁定到匹配的擴展方法(如果存在這樣的方法)。
// Define an interface named IMyInterface. namespace DefineIMyInterface { using System; public interface IMyInterface { // Any class that implements IMyInterface must define a method // that matches the following signature. void MethodB(); } } // Define extension methods for IMyInterface. namespace Extensions { using System; using DefineIMyInterface; // The following extension methods can be accessed by instances of any // class that implements IMyInterface. public static class Extension { public static void MethodA(this IMyInterface myInterface, int i) { Console.WriteLine ("Extension.MethodA(this IMyInterface myInterface, int i)"); } public static void MethodA(this IMyInterface myInterface, string s) { Console.WriteLine ("Extension.MethodA(this IMyInterface myInterface, string s)"); } // This method is never called in ExtensionMethodsDemo1, because each // of the three classes A, B, and C implements a method named MethodB // that has a matching signature. public static void MethodB(this IMyInterface myInterface) { Console.WriteLine ("Extension.MethodB(this IMyInterface myInterface)"); } } } // Define three classes that implement IMyInterface, and then use them to test // the extension methods. namespace ExtensionMethodsDemo1 { using System; using Extensions; using DefineIMyInterface; class A : IMyInterface { public void MethodB() { Console.WriteLine("A.MethodB()"); } } class B : IMyInterface { public void MethodB() { Console.WriteLine("B.MethodB()"); } public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); } } class C : IMyInterface { public void MethodB() { Console.WriteLine("C.MethodB()"); } public void MethodA(object obj) { Console.WriteLine("C.MethodA(object obj)"); } } class ExtMethodDemo { static void Main(string[] args) { // Declare an instance of class A, class B, and class C. A a = new A(); B b = new B(); C c = new C(); // For a, b, and c, call the following methods: // -- MethodA with an int argument // -- MethodA with a string argument // -- MethodB with no argument. // A contains no MethodA, so each call to MethodA resolves to // the extension method that has a matching signature. a.MethodA(1); // Extension.MethodA(object, int) a.MethodA("hello"); // Extension.MethodA(object, string) // A has a method that matches the signature of the following call // to MethodB. a.MethodB(); // A.MethodB() // B has methods that match the signatures of the following // method calls. b.MethodA(1); // B.MethodA(int) b.MethodB(); // B.MethodB() // B has no matching method for the following call, but // class Extension does. b.MethodA("hello"); // Extension.MethodA(object, string) // C contains an instance method that matches each of the following // method calls. c.MethodA(1); // C.MethodA(object) c.MethodA("hello"); // C.MethodA(object) c.MethodB(); // C.MethodB() } } } /* Output: Extension.MethodA(this IMyInterface myInterface, int i) Extension.MethodA(this IMyInterface myInterface, string s) A.MethodB() B.MethodA(int i) B.MethodB() Extension.MethodA(this IMyInterface myInterface, string s) C.MethodA(object obj) C.MethodA(object obj) C.MethodB() */
通常,建議你只在不得已的情況下才實現擴展方法,並謹慎地實現。 只要有可能,必須擴展現有類型的客戶端代碼都應該通過創建從現有類型派生的新類型來達到這一目的。 有關詳細信息,請參閱繼承(C# 編程指南)。
在使用擴展方法來擴展你無法更改其源代碼的類型時,你需要承受該類型實現中的更改會導致擴展方法失效的風險。
如果你確實為給定類型實現了擴展方法,請記住以下幾點:
-
如果擴展方法與該類型中定義的方法具有相同的簽名,則擴展方法永遠不會被調用。
-
在命名空間級別將擴展方法置於范圍中。 例如,如果你在一個名為 Extensions 的命名空間中具有多個包含擴展方法的靜態類,則這些擴展方法將全部由 using Extensions; 指令置於范圍中。
針對已實現的類庫,不應為了避免程序集的版本號遞增而使用擴展方法。 如果要向你擁有源代碼的庫中添加重要功能,應遵循適用於程序集版本控制的標准 .NET Framework 准則。 有關詳細信息,請參閱程序集版本控制。