擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種特殊的靜態方法,但可以像擴展類型上的實例方法一樣進行調用。這是msdn的描述。上面幾句我看好多博客都是這樣開頭的。所以我也這樣開頭。
原本想着上一篇博客回顧了下泛型,將具體的模糊化,這個應該講反射,將模糊的具體化,不過呢看了下反射東西不少,一晚上我也總結不完,還要留點時間打飛機呢。於是想了想就總結下擴展吧。
一、為什么要有擴展方法?
開頭也說了,無需創建新的派生類型、重新編譯或其他方式修改原始類型給現有類或接口添加方法。比如在沒有擴展之前,會經常有一些helper工具類,例如處理字符串、時間的。有了擴展我們可以直接擴展字符串類或時間類就可以了,這樣不用在實例化helper類就能直接處理。
二、擴展方法有什么特征?
擴展方法是靜態方法,是類的一部分,但是實際上沒有放在類的源代碼中。
擴展方法所在的類也必須被聲明為static
C#只支持擴展方法,不支持擴展屬性、擴展事件等。
擴展方法的第一個參數是要擴展的類型,放在this關鍵字的后面,this后面的參數不屬於方法的參數
在擴展方法中,可以訪問擴展類型的所有公共方法和屬性。
擴展方法擴展自哪個類型,就必須是此類型的變量來使用,其他類型無法使用
如果擴展方法和實例方法具有相同的簽名,則優先調用實例方法
擴展自父類上的方法,可以被子類的對象直接使用
擴展自接口上的方法,可以被實現類的對象直接使用
擴展方法最終還是被編譯器編譯成:靜態類.靜態方法()
三、demo
上面幾句基本總結把擴展總結完了,下面做一個demo來說明一下。
1.定義IAnimal接口 聲明void Eat();方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExtensionMethod { public interface IAnimal { void Eat(); } }
2.定義Person類實現接口IAnimal實現void Eat();方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExtensionMethod { public class Person:IAnimal { public void Eat() { Console.WriteLine("Person Eat"); } } }
3.定義擴展方法ExtensionMethod
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExtensionMethod { public static class ExtensionMethod { public static void Eat(this IAnimal iAnimal) { Console.WriteLine("IAnimalExtension Eat"); } public static void Sleep(this IAnimal iAnimal) { Console.WriteLine("IAnimalExtension Sleep"); } public static void Eat(this Person person) { Console.WriteLine("PersonExtension Eat"); } public static void Sleep(this Person person) { Console.WriteLine("PersonExtension Sleep"); } } }
上面在ExtensionMethod類中定義了4個擴展方法,兩個是對接口IAnimal的擴展,兩個是對Person類的擴展。
4.實例化測試
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExtensionMethod { class Program { static void Main(string[] args) { IAnimal p = new Person(); p.Eat(); p.Sleep(); Console.WriteLine("----------------這是底線------------------------"); Person p1 = new Person(); p1.Eat(); p1.Sleep(); Console.WriteLine("----------------這是底線------------------------"); ExtensionMethod.Eat(p); ExtensionMethod.Sleep(p); Console.WriteLine("----------------這是底線------------------------"); ExtensionMethod.Eat(p1); ExtensionMethod.Sleep(p1); Console.ReadLine(); } } }
上面Mian方法中,首先實例化了一個Person對象,賦值給IAnimal類型的變量,調用Eat()和Sleep()方法。然后又實例化了一個Person對象,這次賦值給Person類型的變量。
下面來看下運行結果是不是出乎意料:
p和p1我們可以對比着來分析,對於Eat()方法都是輸出"Person Eat",如果擴展方法和實例方法具有相同的簽名,則優先調用實例方法,這句話正好能解釋為什么。但是對於Sleep()方法,我們可以看到使用IAnimal類型的變量調用的是接口的擴展方法,使用Person類型的變量調用的是Person類型的擴展方法。擴展方法擴展自哪個類型,就必須是此類型的變量來使用,其他類型無法使用,與這句雖然有點出入,但也是蠻符合的。我是這樣理解的:對於同名方法,實例方法優先擴展方法,自身擴展方法優先父類方法。 也可能是因為子類覆蓋了父類的擴展方法。
我們可以把Person對的擴展方法注釋,然后看下運行結果。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExtensionMethod { public static class ExtensionMethod { public static void Eat(this IAnimal iAnimal) { Console.WriteLine("IAnimalExtension Eat"); } public static void Sleep(this IAnimal iAnimal) { Console.WriteLine("IAnimalExtension Sleep"); } //public static void Eat(this Person person) //{ // Console.WriteLine("PersonExtension Eat"); //} //public static void Sleep(this Person person) //{ // Console.WriteLine("PersonExtension Sleep"); //} } }
從上面的結果可以看到,擴展自接口上的方法,可以被實現類的對象直接使用,其實擴展自父類上的方法,可以被子類的對象直接使用和接口類似