前言
一直以來尤其像C#一些常見的語法,本人更願意去探討其內部實現的原理,為什么要這么做呢?只是為了當我真正在開發中運用語法的時候不會因為犯常識性錯誤或者說因為一些注意事項未曾注意到而耽誤一些無謂的時間,同時也能理解的更深入而不是僅僅停留在表面(或許理解也不是太透)。(當然本人能力有限,太高深的東西必定是研究不明白了,也只有這能力了)。
概念
擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種特殊的靜態方法,但可以像擴展類型上的實例方法一樣進行調用。擴展方法是在C#3.0中添加的特性。聽起來有點迷糊,也許你無意之間就在用擴展方法,請看下圖:

上圖中有虛線下角標的方法就是擴展方法,因為其實現了IENumerable<T>接口,所以會有這些方法。那到底我們手動怎么實現呢?
實現
添加一個Person類,代碼如下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } }
現在假設如下場景:如果該項目中關於Person類字段和方法都已確定,但是在該項目完成之前boss發話要給Person添加一個需求,添加一個Preson類共有的 Hobby 方法。由於項目架構都已經確定,所以為了在不修改源代碼的情況下,我們用 擴展方法 來實現。我們新添加一個擴展類 PersonExtension ,添加Hobby方法,如下:
public static class PersonExtension { public static void Hobby(this Person p) { Console.WriteLine("我們都有愛好"); } }
此時我們在控制台實例化對象試試進行訪問該擴展方法Hobby,結論當然是能:

結果打印出 我們都有愛好 !至此我們就完成了擴展方法的基本使用,感覺是不是so easy!但是我就有如下幾個疑問:
(1)怎么知道我們編寫的是擴展方法的呢?
(2)編寫的擴展方法編譯器到底是怎么找到的呢?
(3)是實例方法先執行還是擴展方法先執行呢?
(4)這個 this關鍵字到底有什么作用呢?
(5)編寫擴展方法時我們又應該注意些什么呢?
請看下文,下面我們一一進行探討並解決。
實現原理
說到看其原理,依然是借助高大上的反編譯工具了。我們看 p.Hobby(); 這段代碼對應的IL代碼是什么就夠了,請看其對應的IL代碼:

最終調用的依然是調用 PersonExtension.Hobby() ,說明 擴展方法其本質是靜態方法 看來並沒有什么特別之處。在Pserson中時常看見這樣的構造函數
public Person(string name, int age) { this.Name = name; this.Age = age; }
所以從中我們也可以得知,誰實例化了Person誰就指代this,那同樣可以理解擴展方法中的this,就是p實例化了Person那這個this就是指向P了,有時候想想語言有時候確實是相通的,此時我想到在JavaScript中不是有個Call方法嗎,如果不清楚的話,我拷貝了正在學習中的JavaScript代碼,如下:
function person(age,name){ this.age=age; this.name=name; } var obj=new Object(); person.call(obj,12,'小黑'); console.log(obj.age); console.log(obj.name); /* 創建空對象obj,此時調用person類的call()方法,並將obj傳遞進去,此時obj成為其調用上下文,此時this即obj,最終能打印出12和小黑 */
不難看出實例化的對象obj通過call方法,就指向了person中this!我們回到 p.Hobby(); 等價於 PersonExtension.Hobby(p) ,就是將你實例化的對象傳到了擴展方法中的this中。那么問題來了,既然擴展方法也是方法,如果我們在Person類中定義一個方法那先是訪問你定義的實例方法還是先訪問你定義的擴展方法呢?這個疑問先遺留在這里,我們繼續往下看,我們剛才看到 p.Hobby()實際上是調用的PersonExtension.Hobby(); 那我們接下來看看 PersonExtension.Hobby() 這個方法里面到底有什么呢?其IL代碼如下:

我們注意到這個里面有個 ExtensionAttribute 標記,就字面意思就叫做【擴展特性】吧,那我們猜想一下,是不是是不是通過這個【擴展特性】來實現的擴展方法了,我們反過來看,顯然我們實現了擴展方法,里面肯定有這個特性所在的程序集,要是我們可以將其特性所在的程序集進行移除,生成錯誤的話,是不是就說明我們的論斷正確呢?試試即可,將會報錯如下:
無法定義新的擴展方法,因為找不到編譯器所需的類型“System.Runtime.CompilerServices.ExtensionAttribute。”是否缺少引用?
所以通過上述我們知道了通過添加 System.Runtime.CompilerServices 程序集里的 ExtensionAttribute 來實現擴展方法。此時不僅心生疑問,用它來實現擴展方法,那么反問一句,它怎么知道它是擴展方法的呢?此時只剩this關鍵字了,於是我去掉擴展方法中的 this 關鍵字重新生成試試,結果如下:

這就說明通過 this 關鍵字來識別為擴展方法,同時此時運行時編譯器無法識別此語法,所以編譯器則將通過上述IL代碼中的特性來轉換為運行時識別的語法。
接下來問題又來了 ,那它是先執行實例方法還是先執行擴展方法呢?我們接下來,在該類中添加同名的實例方法Hobby(),如下:
public void Hobby() { Console.WriteLine("這是實例中的我們都有愛好"); }
此時我們再調用此方法,結果在控制台打印如下:

說明 實例方法優先於擴展方法執行並且可以有同名的實例方法和擴展方法 !那么問題又來了,它是怎么找到擴展方法的呢?我們知道當調用此p.Hobby()方法時,首先肯定會去p對象去找Hobby()方法,如果有,則調用它的實例方法,如果未找到此時再去找擴展方法,問題是我們只知道通過 this 來標識為擴展方法,這個時候就得看我們的約定了,靜態方法所在的類必須是靜態類,我們可以想象,如果是普通類的話是不是得一個一個的找,這樣豈不是很消耗性能,這是微軟大大想要看到的嗎?當然不是,要是為靜態類,只需要到靜態類去找再去靜態方法去找標識為this關鍵字的方法,這時找到了就說明這個方法是擴展方法。
注意事項
(1) 我們再定義一個Bob類,繼承該Person類,並在控制台嘗試調用該擴展方法,代碼如下
public class Bob : Person { } Bob b = new Bob(); b.Hobby();
結果是可以訪問的,說明: 擴展方法能夠被繼承
(2) 當我們將擴展方法的訪問修飾符修改成private會出錯,如下:

說明 其訪問修飾符必須是public
(3)當我們在控制台嘗試傳入空引用對象調用該擴展方法時,擴展方法和控制台調用代碼如下,看看結果如何:
public static class PersonExtension { public static void Hobby(this Person p) { Console.WriteLine("我們都有愛好"); } } Person p = null; p.Hobby();
此時正常調用,說明空引用可以調用擴展方法,但是將擴展方法修改如下,則會出錯:未將對象設置到對象的實例。
public static class PersonExtension { public static void Hobby(this Person p) { Console.WriteLine(p.Name); } }
說明 允許空引用調用擴展方法,若擴展方法中使用了傳入的實例成員,則會出現異常
總結
聲明擴展方法的必須條件
方法必須定義在頂級的靜態類中,並且該靜態類必須直接處在命名空間下而且不能為泛型類(即方法必須放在非嵌套、非泛型的靜態類中)
方法必須是靜態的並且第一個參數用this關鍵字修飾,這個參數被叫做【參數實例】
方法的訪問修飾符必須是public
至少有一個參數
不能有其他參數修飾第一個參數(如ref,out等等)
第一個參數的類型不能是指針類型
注意事項
實例方法優先於擴展方法執行(允許有同名的實例方法和擴展方法)
在空引用上可以調用擴展方法
擴展方法能夠被繼承
擴展方法相關原理
其本質是靜態方法
this關鍵字的作用:(1)指向當前擴展方法中第一個參數類型的實例(2)作為標記,標記此方法為擴展方法
查找擴展方法:如果對象的實例中有該實例方法,調用它的實例方法,如果未找到此時再去找擴展方法,問題是我們只知道通過 this 來標識為擴展方法,這個時候就得看我們的約定了,只需要到靜態類去找再去靜態方法去找標識為this關鍵字的方法,這時找到了就說明這個方法是擴展方法。
