1、寫在前面 今天群里一個小伙伴問了這樣一個問題,擴展方法與實例方法的執行順序是什麼樣子的,誰先誰後(這個問題將會在文章末尾回答),所以寫了這邊文章,力圖從原理角度解釋擴展方法及其使用。 以下為主要內容: 什麼是擴展方法 擴展方法實現及其原理 擴展方法的使用及其註意事項 什麼是擴展方法 擴展方法實現 ...
1、寫在前面 今天群里一個小伙伴問了這樣一個問題,擴展方法與實例方法的執行順序是什麼樣子的,誰先誰後(這個問題將會在文章末尾回答),所以寫了這邊文章,力圖從原理角度解釋擴展方法及其使用。 以下為主要內容:
-
什麼是擴展方法
-
擴展方法實現及其原理
-
擴展方法的使用及其註意事項
一般而言,擴展方法為現有類型添加新的方法(從面向對象的角度來說,是為現有對象添加新的行為)而無需修改原有類型,這是一種無侵入而且非常安全的方式,同時擴展方法無法訪問擴展類中的任何隱私數據。擴展方法是靜態的,它的使用和其他實例方法幾乎沒有什麼區別。常見的擴展方法有Linq擴展、有IEnumerable擴展等。
先讓我們來感受一下,擴展方法:
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleLab { class Program { static void Main(string[] args) { List<int> lst = new List<int> { 1, 2, 3, 4 }; lst.OrderBy(p => p); Console.WriteLine(lst.Aggregate(string.Empty, (next, str) => next += str + ",")); Console.Read(); } } }
輸出:
以上用到了List擴展裡面的OrderBy和Aggregate,不得不說,.NET在這方面做得非常精緻。可是話說回來,它是如何實現的呢。接下來我們重點關註一下
3、擴展方法實現及其原理 我們先看一個關於自定義擴展方法的範例using ConsoleExtension; using System; namespace ConsoleExtension { internal static class StringExtension { internal static int ToInt(this string str) { if (int.TryParse(str, out int result)) { return result; } throw new ArgumentException("無效參數"); } } } namespace ConsoleLab { class Program { static void Main(string[] args) { Console.WriteLine("2".ToInt()); Console.Read(); } } }
通過以上示例,我們可以總結如下特點
- 定義的擴展方法必須包括在靜態類中,其本身也是靜態的
- 改靜態的可見性至少與所在類的可見性相同,也就是同時public或者internal
- 此方法的第一個參數指定方法所操作的類型;此參數前面必須加上 this 修飾符
- 在調用代碼中,添加 using 指令,用於指定包含擴展方法類的命名空間
- 調用方式和調用實例方法一樣
- 空對象是可以直接調用擴展方法的(此處就不再加示例展示了,有興趣的可以自己驗證)
.class public auto ansi abstract sealed beforefieldinit ConsoleLab.StringExtension extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method public hidebysig static int32 ToInt ( string str ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute
::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2050 // Code size 31 (0x1f) .maxstack 2 .locals init ( [0] int32, [1] bool, [2] int32 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldloca.s 0 IL_0004: call bool [mscorlib]System.Int32::TryParse(string, int32&) IL_0009: stloc.1 IL_000a: ldloc.1 IL_000b: brfalse.s IL_0012 IL_000d: nop IL_000e: ldloc.0 IL_000f: stloc.2 IL_0010: br.s IL_001d IL_0012: ldstr "無效參數" IL_0017: newobj instance void [mscorlib]System.ArgumentException::.ctor(string) IL_001c: throw IL_001d: ldloc.2 IL_001e: ret } // end of method StringExtension::ToInt } // end of class ConsoleLab.StringExtension以上是StringExtension.ToInt()後的效果,和普通方法其實也沒有什麼區別
.class private auto ansi beforefieldinit ConsoleLab.Program extends [mscorlib]System.Object { // Methods .method private hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x207b // Code size 24 (0x18) .maxstack 8 .entrypoint IL_0000: nop IL_0001: ldstr "2" IL_0006: call int32 ConsoleLab.StringExtension::ToInt(string) IL_000b: call void [mscorlib]System.Console::WriteLine(int32) IL_0010: nop IL_0011: call int32 [mscorlib]System.Console::Read() IL_0016: pop IL_0017: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2094 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: ret } // end of method Program::.ctor } // end of class ConsoleLab.Program以上是Program類反編譯後的源碼,其調用方式跟調用靜態方法沒有什麼區別 到了這個地方,我們可以看到,編譯後的代碼調用其實和調用一個類的靜態方法一樣,也就是StringExtension::ToInt。只不過是.NET標記了這個方法為擴展方法,也就是我們看到的System.Runtime.CompilerServices.ExtensionAttribute 4、寫到最後
擴展方法雖然看起來挺不錯的,但是我們也要謹慎的使用,因為擴展的源對象如果發生了變化,就會導致bug的出現如果確實為給定類型實現了擴展方法,請記住以下幾點:
-
如果擴展方法與其實例方法具有相同的簽名,擴展方法將無法被調用,這樣也避免了擴展方法對原有代碼所帶來的損害,所以不存在誰先誰後的問題,這就回答了,文章開頭所提出的問題
-
如果擴展方法和調用方不在同一個命名空的,需要使用
using導入
以上為本篇文章的主要內容,希望大家多提提意見,如果喜歡記得點個贊哦