.NET3.5給咱們提供了擴展方法的概念,它的功能是在不修改要添加類型的原有結構時,容許你爲類或結構添加新方法。程序員
思考:那麼究竟爲何須要擴展方法呢,爲何不直接修改原有類型呢?數組
首先,假設咱們的項目中有一個類,後來過了一段時間,咱們明確的知道須要爲該類添加一個新功能,考慮這個需求有兩個解決辦法:ide
(1)直接修改當前類的定義函數
這樣作的缺點是,破壞向後的兼容性,可能之前使用的舊代碼沒法經過編譯。好比說舊代碼使用了一個Methed(int,int)的方法,可是爲了知足新功能咱們如今修改爲了Methed(int,int,int),多增長了一個參數,這樣原有的舊代碼就沒法經過編譯。ui
(2)以當前類爲基類進行派生,在子類中進行實現this
這樣作也有缺點,那就是假如功能須要修改時,咱們須要維護兩個地方,一個是父類,一個是子類,增長了代碼維護工做量spa
這時,新的特性擴展方法解決了以上兩個問題,而且還解決了當有些類的實現是第三方的,咱們沒法修改源代碼狀況下,以及某些類是不可繼承的,沒法派生的,這兩種狀況下任然可使用擴展方法來添加新功能。使用擴展方法,能夠在不建立子類和直接修改類型的狀況下修改類型。code
定義擴展方法必須遵照兩個Static和一個this:對象
1.必須把擴展方法定義在靜態類中,每一個擴展方法也必須聲明爲靜態的 |
2.全部擴展方法必需要使用this關鍵字對第一個參數進行修飾 |
擴展方法的實現以下圖所示,咱們要給StringBuilder系統類型擴展一個功能用於提取字符串對象中的某個字符的索引blog
在實例層次上調用擴展方法的意思就是,在被擴展對象的實例上進行調用而不是使用咱們定義的靜態類調用。具體怎樣調用,請看一下代碼。
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版)
因爲本人才學識淺,描述不免紕漏,若有錯誤,歡迎指出。麼麼!