C#3.0新增功能04 擴展方法

  擴展方法使你可以向現有類型「添加」方法,而無需建立新的派生類型、從新編譯或以其餘方式修改原始類型。 擴展方法是一種特殊的靜態方法,但能夠像擴展類型上的實例方法同樣進行調用。 對於用 C#、F# 和 Visual Basic 編寫的客戶端代碼,調用擴展方法與調用在類型中實際定義的方法沒有明顯區別。html

實現和調用自定義擴展方法

  介紹如何爲任意 .NET 類型實現自定義擴展方法。 客戶端代碼能夠經過如下方法使用擴展方法,添加包含這些擴展方法的 DLL 的引用,以及添加 using 指令,該指令指定在其中定義擴展方法的命名空間。express

定義和調用擴展方法

  1. 定義包含擴展方法的靜態編程

    此類必須對客戶端代碼可見。 有關可訪問性規則的詳細信息,請參閱訪問修飾符api

  2. 將擴展方法實現爲靜態方法,而且使其可見性至少與所在類的可見性相同。安全

  3. 此方法的第一個參數指定方法所操做的類型;此參數前面必須加上 this 修飾符。app

  4. 在調用代碼中,添加 using 指令,用於指定包含擴展方法類的命名空間dom

  5. 和調用類型的實例方法那樣調用這些方法。ide

    請注意,第一個參數並非由調用代碼指定,由於它表示要在其上應用運算符的類型,而且編譯器已經知道對象的類型。測試

示例

如下示例實現 CustomExtensions.StringExtension 類中名爲 WordCount 的擴展方法。 此方法對 String 類進行操做,該類指定爲第一個方法參數。 將 CustomExtensions 命名空間導入應用程序命名空間,並在 Main 方法內部調用此方法。ui

 1 using System.Linq;
 2 using System.Text;
 3 using System;
 4 
 5 namespace CustomExtensions
 6 {
 7     // 擴展方法必須定義在靜態類的內部
 8     public static class StringExtension
 9     {
10         // 這是一個擴展方法:第一個參數必須使用 this 關鍵字修飾,指定爲其定義方法的類型
11         public static int WordCount(this String str)
12         {
13             return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
14         }
15     }
16 }
17 
18 namespace Extension_Methods_Simple
19 {
20     // 導入擴展方法的命名空間
21     using CustomExtensions;
22     class Program
23     {
24         static void Main(string[] args)
25         {
26             string s = "The quick brown fox jumped over the lazy dog.";
27             int i = s.WordCount(); // 調用該方法,就像它是該類型上的實例方法同樣。注意,第一個參數不是由調用代碼指定的
28             System.Console.WriteLine("Word count of s is {0}", i);
29         }
30     }
31 }
.NET Framework 安全性

  擴展方法不存在特定的安全漏洞。 始終不會將擴展方法用於模擬類型的現有方法,由於爲了支持類型自己定義的實例或靜態方法,已解決全部名稱衝突。 擴展方法沒法訪問擴展類中的任何隱私數據。

  在代碼中,可使用實例方法語法調用該擴展方法。 可是,編譯器生成的中間語言 (IL) 會將代碼轉換爲對靜態方法的調用。 所以,並未真正違反封裝原則。 實際上,擴展方法沒法訪問它們所擴展的類型中的私有變量。

一般,你更多時候是調用擴展方法而不是實現你本身的擴展方法。 因爲擴展方法是使用實例方法語法調用的,所以不須要任何特殊知識便可從客戶端代碼中使用它們。 若要爲特定類型啓用擴展方法,只需爲在其中定義這些方法的命名空間添加 using 指令。 例如,若要使用標準查詢運算符,請將此 using 指令添加到代碼中:

using System.Linq;

(你可能還必須添加對 System.Core.dll 的引用。)你將注意到,標準查詢運算符如今做爲可供大多數 IEnumerable<T> 類型使用的附加方法顯示在 IntelliSense 中。

在編譯時綁定擴展方法

可使用擴展方法來擴展類或接口,但不能重寫擴展方法。 與接口或類方法具備相同名稱和簽名的擴展方法永遠不會被調用。 編譯時,擴展方法的優先級老是比類型自己中定義的實例方法低。 換句話說,若是某個類型具備一個名爲 Process(int i) 的方法,而你有一個具備相同簽名的擴展方法,則編譯器老是綁定到該實例方法。 當編譯器遇到方法調用時,它首先在該類型的實例方法中尋找匹配的方法。 若是未找到任何匹配方法,編譯器將搜索爲該類型定義的任何擴展方法,而且綁定到它找到的第一個擴展方法。 下面的示例演示編譯器如何肯定要綁定到哪一個擴展方法或實例方法。

示例

下面的示例演示 C# 編譯器在肯定是將方法調用綁定到類型上的實例方法仍是綁定到擴展方法時所遵循的規則。 靜態類 Extensions 包含爲任何實現了 IMyInterface 的類型定義的擴展方法。 類 AB 和 C 都實現了該接口。

MethodB 擴展方法永遠不會被調用,由於它的名稱和簽名與這些類已經實現的方法徹底匹配。

若是編譯器找不到具備匹配簽名的實例方法,它會綁定到匹配的擴展方法(若是存在這樣的方法)。

  1 namespace DefineIMyInterface
  2 {
  3     using System;
  4 
  5     public interface IMyInterface
  6     {
  7         // 實現 IMyInterface 接口的任何類都必須定義與如下簽名匹配的方法
  8         void MethodB();
  9     }
 10 }
 11 
 12 
 13 // 定義三個實現 IMyInterface 的類,而後使用它們來測試擴展方法。
 14 namespace ExtensionMethodsDemo1
 15 {
 16     using System;
 17     using Extensions;
 18     using DefineIMyInterface;
 19 
 20     class A : IMyInterface
 21     {
 22         public void MethodB() { Console.WriteLine("A.MethodB()"); }
 23     }
 24 
 25     class B : IMyInterface
 26     {
 27         public void MethodB() { Console.WriteLine("B.MethodB()"); }
 28         public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
 29     }
 30 
 31     class C : IMyInterface
 32     {
 33         public void MethodB() { Console.WriteLine("C.MethodB()"); }
 34         public void MethodA(object obj)
 35         {
 36             Console.WriteLine("C.MethodA(object obj)");
 37         }
 38     }
 39 
 40     // 定義 IMyInterface 的擴展方法
 41     namespace Extensions
 42 {
 43     using System;
 44     using DefineIMyInterface;
 45 
 46     // 實現 IMyInterface 的任何類的實例均可以訪問如下擴展方法
 47     public static class Extension
 48     {
 49         public static void MethodA(this IMyInterface myInterface, int i)
 50         {
 51             Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
 52         }
 53 
 54         public static void MethodA(this IMyInterface myInterface, string s)
 55         {
 56             Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
 57         }
 58 
 59         // ExtensionMethodsDemo1 類中永遠不會調用此方法,
 60         // 由於三個類A、B和C中的每個都實現了名爲methodB的方法,該方法具備匹配的簽名。
 61         public static void MethodB(this IMyInterface myInterface)
 62         {
 63             Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
 64         }
 65     }
 66 }
 67 
 68     class ExtMethodDemo
 69     {
 70         static void Main(string[] args)
 71         {
 72             A a = new A();
 73             B b = new B();
 74             C c = new C();
 75 
 76 
 77             // A 不包含 MethodA,所以對 MethodA 的每一個調用都解析爲具備匹配簽名的擴展方法
 78             a.MethodA(1);           // Extension.MethodA(IMyInterface, int)
 79             a.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 80 
 81             // A 有一個方法與對 MethodB 的如下調用的簽名匹配
 82             a.MethodB();              // A.MethodB()
 83 
 84             // B 具備與如下方法調用的簽名匹配的方法
 85             b.MethodA(1);           // B.MethodA(int)
 86             b.MethodB();             // B.MethodB()
 87 
 88             // B 沒有用於如下調用的匹配方法,可是類擴展名有
 89             b.MethodA("hello");     // Extension.MethodA(IMyInterface, string)
 90              
 91             // C 包含一個匹配如下每一個方法調用的實例方法
 92             c.MethodA(1);           // C.MethodA(object)
 93             c.MethodA("hello");     // C.MethodA(object)
 94             c.MethodB();                // C.MethodB()
 95         }
 96     }
 97 }
 98 
 99 /* 輸出:
100     Extension.MethodA(this IMyInterface myInterface, int i)
101     Extension.MethodA(this IMyInterface myInterface, string s)
102     A.MethodB()
103     B.MethodA(int i)
104     B.MethodB()
105     Extension.MethodA(this IMyInterface myInterface, string s)
106     C.MethodA(object obj)
107     C.MethodA(object obj)
108     C.MethodB()
109  */
 
通用準則

一般,建議你只在不得已的狀況下才實現擴展方法,並謹慎地實現。 只要有可能,必須擴展示有類型的客戶端代碼都應該經過建立從現有類型派生的新類型來達到這一目的。 有關詳細信息,請參閱繼承

在使用擴展方法來擴展你沒法更改其源代碼的類型時,你須要承受該類型實現中的更改會致使擴展方法失效的風險。

若是確實爲給定類型實現了擴展方法,請記住如下幾點:

  • 若是擴展方法與該類型中定義的方法具備相同的簽名,則擴展方法永遠不會被調用。

  • 在命名空間級別將擴展方法置於範圍中。 例如,若是你在一個名爲 Extensions 的命名空間中具備多個包含擴展方法的靜態類,則這些擴展方法將所有由 using Extensions; 指令置於範圍中。

針對已實現的類庫,不該爲了不程序集的版本號遞增而使用擴展方法。 若是要向你擁有源代碼的庫中添加劇要功能,應遵循適用於程序集版本控制的標準 .NET Framework 準則。有關詳細信息,請參閱程序集版本控制

其餘技術請參閱

 

相關文章
相關標籤/搜索