上一篇隨筆 .NET 擴展方法 (一) 已經對 擴展方法有了大致的介紹,這篇算是一個補充,讓我們來看一下擴展方法的幾個細節:
一、擴展方法具有繼承性
當使用擴展方法擴展一個類型的時候,其也擴展了派生類,所以上一篇的遺留問題“如果給object添加一個擴展方法會出現什么效果呢?” 的
答案就是——所有類型都將擴展該方法。object類已經經受住了時間的考驗,我們似乎也找不到更合適的理由來擴展object類。從另外的
角度考慮,如果擴展了object類,很有可能會給“智能敏感提示”造成污染,以至于填充了過多的垃圾信息(因為許多類型也根本用不到該方法)。
二、擴展方法允許和擴展類型原有方法相等效的方法存在 (此處是個雷)
由于漢語語言表述的所帶來的不易理解性,我們還是直接用代碼來解釋吧,如下的代碼片段:
public static class StringExtentsion { public static string ToString(this string str) { return "Extentsion" + str; } } class PRogram { static void Main(string[] args) { string str = "test"; Console.WriteLine(str.ToString()); // 輸出結果為: test,也就說編譯器會優先選用原有類的實例化方法,如果沒找到匹配方法再尋找擴展方法 Console.Read(); } }由上述的代碼片段可以知:StringExtentsion類中擴展方法ToString 和 String類的原有的ToString方法 對于客戶端代碼而言,它們的語法表象是
一樣的,但本質上一個是StringExtentsion類的靜態方法,一個是String類的實例化方法。然而編譯運行沒有產生錯誤,更沒有產生警告。所以在
這種情況下很容“埋雷”,一不小心就會中招。有人也許會說:我注意一下不要和.NET類庫的方法重名就可以了。但是你能保證 .NET 6、甚至.NET 10
的方法名和你寫的絕對不重名嗎?所以,擴展方法存在著版本控制的問題。
三、擴展方法允許在兩個或多個類中共存相同的擴展方法
public static class StringExtentsion { public static bool IsEmpty(this string str) { return string.IsNullOrWhiteSpace(str); } } public static class OtherStringExtentsion { public static bool IsEmpty(this string str) { return string.IsNullOrWhiteSpace(str); } } class Program { static void Main(string[] args) { string str = null; //bool result = str.IsEmpty(); // 這種寫法會產生編譯錯誤:不明確的調用,因為它能找到兩個擴展方法 bool result = StringExtentsion.IsEmpty(str); // 必須顯示使用類調用靜態方法 Console.WriteLine(result); Console.Read(); } }
四、擴展其他類型的方法
擴展方法不僅可以擴展類類型,而且可以擴展接口、枚舉、委托、結構、數組及對應的泛型類型 等類型。
在這里著重說一下擴展接口,任何 “實現了接口的類型對象” 都可以調用接口上的擴展方法。針對這一特點,我們完全可以將實現接口
的共用代碼放進擴展方法里,實現代碼復用。從形式上來看,該特點是 “單繼承 + 接口” 與 “多繼承”的中間產物,已經有了“多繼承”的影子。
五、委托與擴展方法
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace MethodDemo{ public static class StringExtentsion { public static string ShowString(this string str) { return "ShowString:" + str; } } class Program { static void Main(string[] args) { string str = "meng"; Func<string> fun = str.ShowString; fun(); Func<string> fun2 = str.ToString; fun2(); Console.Read(); } }}
如果你已經看過了 .NET 擴展方法 (一) ,也許你能猜到我接下來想說什么了吧。沒錯,看一下IL代碼吧,看看編譯器在背后搞了哪些“怪”。
順便借助這個機會,說幾個IL指令。
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // 代碼大小 55 (0x37) 5 .maxstack 2 6 7 // 初始化3個局部變量 8 .locals init ([0] string str, 9 [1] class [mscorlib]System.Func`1<string> fun,10 [2] class [mscorlib]System.Func`1<string> fun2)11 IL_0000: nop12 13 //將 "meng" 這個字符串對象的引用 入棧14 IL_0001: ldstr "meng"15 16 // 將棧頂的值賦值給第0個局部變量(即 str),棧頂值出棧17 IL_0006: stloc.018 19 // 將第0個局部變量入棧 (即 str 入棧)20 IL_0007: ldloc.021 22 // 將 MethodDemo.StringExtentsion類的靜態方法ShowString的指針入棧23 IL_0008: ldftn string MethodDemo.StringExtentsion::ShowString(string)24 25 // 調用構造函數 new一個 Func<String>類型的委托26 IL_000e: newobj instance void class [mscorlib]System.Func`1<string>::.ctor(object,27 native int)28 IL_0013: stloc.129 IL_0014: ldloc.130 31 // 調用fun對象的Invoke方法32 IL_0015: callvirt instance !0 class [mscorlib]System.Func`1<string>::Invoke()33 IL_001a: pop34 IL_001b: ldloc.035 IL_001c: dup36 37 // 將str 對象的實例化方法ToString方法的指針入棧38 IL_001d: ldvirtftn instance string [mscorlib]System.Object::ToString()39 IL_0023: newobj instance void class [mscorlib]System.Func`1<string>::.ctor(object,40 native int)41 IL_0028: stloc.242 IL_0029: ldloc.243 IL_002a: callvirt instance !0 class [mscorlib]System.Func`1<string>::Invoke()44 IL_002f: pop45 IL_0030: call int32 [mscorlib]System.Console::Read()46 IL_0035: pop47 IL_0036: ret48 } // end of method Program::Main
核心的IL指令已經給予了差不多的注釋,根據IL指令可以得出結果:我們又被編譯器“欺騙”了一次,fun對象保存的方法指針是
MethodDemo.StringExtentsion類的靜態方法ShowString的指針。fun2對象保存的方法的指針str對象的ToString方法的指針。
新聞熱點
疑難解答