.NET3.5給我們提供了擴展方法的概念,它的功能是在不修改要添加類型的原有結構時,允許你為類或結構添加新方法。
思考:那麼究竟為什麼需要擴展方法呢,為什麼不直接修改原有類型呢?
首先,假設我們的項目中有一個類,後來過了一段時間,我們明確的知道需要為該類添加一個新功能,考慮這個需求有兩個解決辦法:
(1)直接修改當前類的定義
這樣做的缺點是,破壞向後的兼容性,可能以前使用的舊代碼無法通過編譯。比如說舊代碼使用了一個Methed(int,int)的方法,但是為了滿足新功能我們現在修改成了Methed(int,int,int),多增加了一個參數,這樣原有的舊代碼就無法通過編譯。
(2)以當前類為基類進行派生,在子類中進行實現
這樣做也有缺點,那就是假如功能需要修改時,我們需要維護兩個地方,一個是父類,一個是子類,增加了代碼維護工作量
這時,新的特性擴展方法解決了以上兩個問題,並且還解決了當有些類的實現是第三方的,我們無法修改源代碼情況下,以及某些類是不可繼承的,無法派生的,這兩種情況下任然可以使用擴展方法來添加新功能。使用擴展方法,可以在不創建子類和直接修改類型的情況下修改類型。
定義擴展方法必須遵守兩個Static和一個this:
1.必須把擴展方法定義在靜態類中,每個擴展方法也必須聲明為靜態的 2.所有擴展方法必須要使用this關鍵字對第一個參數進行修飾
擴展方法的實現如下圖所示,我們要給StringBuilder系統類型擴展一個功能用於提取字符串對象中的某個字符的索引
在實例層次上調用擴展方法的意思就是,在被擴展對象的實例上進行調用而不是使用我們定義的靜態類調用。具體怎樣調用,請看一下代碼。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExStensionMethd { class Program { static void Main(string[] args) { StringBuilder tmpStr = new StringBuilder("12323"); Console.WriteLine(tmpStr.StringIndef('2'));//這裡我們使用的是StringBuilder的實例tmpStr來直接調用 Console.ReadKey(); } } /// <summary> /// 擴展方法靜態類 /// </summary> static class ExtionClass { public static int StringIndef(this StringBuilder str, char tmpChar) { int index = 0; for (; index < str.Length; index++) { if (str[index].Equals(tmpChar)) { return index; } } return -1; } } }實例層次調用擴展方法
說明:需要特別說明的是,擴展方法可以擁有多個參數,但第一個參數的位置始終是屬於為擴展對象的,不能改變。也就是說只有第一個參數可以並且必須用this關鍵字修飾,其他的參數視為方法的普通參數。
定義擴展方法,也就是說定義擴展方法靜態類時,我們必須為其指定命名空間,如果該命名空間與要使用擴展方法的命名空間不同,則需要導入命名空間(使用using關鍵字)。建議擴展方法定義成將要擴展類型的相同的命名空間,比如我們上面例子中的系統類型StringBulder的命名空間為System.Text,我們完全可以添加一個新類,然後定義命名空間為System.Text,也就是說最好是全局的、最外層的命名空間,這樣做的好處我們將在下面闡述。
擴展方法的實質其實是由編譯器來采用鏡像原理來實現的,並沒有改變原有類型或者是附加什麼額外的東西,查看下圖譯生成後的IL代碼,可以看到我們用紅色框標記出來的地址,call int32 ExStensionMethd.ExtionClass::StringIndef(class [mscorlib]System.Text.StringBuilder,char)其實質仍然調用的原靜態類的公共靜態方法而已。
所以代碼中的tmpStr.StringIndef('2'))和ExtionClass.StringIndef(tmpStr,'2')是等效的,然而為什麼我們可以直接使用實例調用呢,這是因為編譯器默默地做了工作。
編譯器工作---------------------------------------
tmpStr.StringIndef('2')
當編譯看到以下代碼,編譯器分兩步工作:
(1) 編譯器檢查tmpStr當前類型,也就是StringBulder類以及StringBulder任何基類是否具有所匹配的名為StringIndef包含一個char參數的函數,如果找到,則生成IL代碼並Call它;
(2) 如果沒有找到匹配的方法,就繼續檢查是否有任何靜態類定義了名為StringIndef的靜態方法,並且這個方法必須第一個參數是用this關鍵字修飾,參數類型為StringBulder的。找到時生成相應的IL代碼來調用它
所以這正是我們定義擴展方法的意義,因為編譯器就是按照規則來匹配相應的方法為其生成IL代碼,然而就算我們定義了擴展方法,其他的程序員也不知道,這樣豈不是多此一舉嗎,不是這樣。作為宇宙對強大編譯器的VS,它使用了"智能感知“的功能來簡化我們對擴展方法的使用,當我們使用擴展類型實例時,當點號按下時,VS自動添加上擴展方法讓我們選擇,對我們程序員來說,就好像是直接使用了擴展對象的原有方法一樣。這就是VS編譯器為我們所做的。
擴展方法的擴展對象可以是類,接口,委托類型等。當擴展接口時,則所有實現了此接口的類,都擁有此擴展方法。
class Program { static void Main(string[] args) { "123123123123".ShowItems();//字符串 new[] { 1, 2, 3, 4, }.ShowItems();//int數組 new List<int> { 1, 2, 3, 4 }.ShowItems();//List容器 Console.ReadKey(); } } /// <summary> /// 擴展方法靜態類 /// </summary> static class ExtionClass { public static void ShowItems<T>(this IEnumerable<T> colletion) { foreach (var item in colletion) { if (item is string) Console.WriteLine(item); else Console.WriteLine(item.ToString()); } } }接口的擴展方法
參考資料:精通C#(第6版)
CLR via C#(第4版)
由於本人才學識淺,描述難免纰漏,如有錯誤,歡迎指出。麼麼!