引言
在 MSDN 中對 System.Linq.Enumerable 類的 AsEnumerable 方法相關描述如下所示:
實際上,這個方法只有一條語句,就是原樣返回它的唯一參數:
- reutrn source
使用 .NET Reflector 查看 .NET Framework 4.5 Class Library,就很清楚了:
測試程序
這種只是原樣返回它的唯一參數的方法有什么用呢?讓我們來看一個例子吧:
1 using System; 2 using System.Linq; 3 using System.Collections.Generic; 4 5 namespace Skyiv.Test 6 { 7 static class LinqTester 8 { 9 class Firster<T> : List<T> { public T First() { return default(T); } } 10 static void Test<T>(Firster<T> x) { Console.WriteLine("F:" + x.First()); } 11 static void Test<T>(List<T> x) { Console.WriteLine("L:" + x.First()); } 12 static void Test<T>(IEnumerable<T> x) { Console.WriteLine("I:" + x.First()); } 13 14 static void Main() 15 { 16 var a = new Firster<int> { 2, 3, 5 }; 17 Test(a); 18 Test(a as List<int>); 19 Test(a.AsEnumerable()); 20 } 21 } 22 }
在上述程序中:
- Firster<T> 類是 List<T> 類的派生類。
- List<T> 類實現了 IEnumerable<T> 接口。
- IEnumerable<T> 接口有一個 First 擴展方法 (定義在 System.Linq.Enumerable 類中)。
- Firster<T> 類定義了一個 First 實例方法。
在 Arch Linux 的 Mono 環境下編譯和運行:
work$ dmcs LinqTester.cs && mono LinqTester.exe F:0 L:2 I:2
上述運行結果解釋如下:
- 第 17 行調用第 10 行的 Test 方法,參數類型是 Firster<T>,於是調用 Firster<T> 類的 First 實例方法,輸出: F:0。
- 第 18 行調用第 11 行的 Test 方法,參數類型是 List<T>,在 List<T> 類中沒有找到 First 方法,由於 List<T> 類實現了 IEnumerable<T> 接口,所以調用 IEnumerable<T> 接口的 First 擴展方法,輸出: L:2。
- 第 19 行調用第 12 行的 Test 方法,參數類型是 IEnumerable<T>,於是調用 IEnumerable<T> 接口的 First 擴展方法,輸出: I:2。
如果在上述程序中,分別進行以下操作:
- 刪除第 10 行的語句
- 刪除第 11 行的語句
- 刪除第 10 行和第 11 行的語句
再重新編譯和運行,分別會有什么結果呢?
另外一個測試程序
前面的測試程序引用了 System.Linq 和 System.Collections.Generic 命名空間,涉及到的東東比較復雜。下面我們來一個簡單點的測試程序:
1 using System; 2 3 namespace Skyiv.Test 4 { 5 class Thing { public string GetId() { return "Thing"; } } 6 7 static class Extensions 8 { 9 public static object AsObject(this object x) { return x; } 10 public static string GetId(this object x) { return "Object"; } 11 } 12 13 static class Tester 14 { 15 static void Main() 16 { 17 var a = new Thing(); 18 var b = a.AsObject(); 19 Console.WriteLine(a.GetId()); 20 Console.WriteLine(b.GetId()); 21 } 22 } 23 }
在上述程序中:
- Thing 類定義了一個 GetId 實例方法。
- Extensions 類為 object 類定義了一個 GetId 擴展方法。
- Extensions 類為 object 類定義了一個 AsObject 擴展方法。
對於上述程序來說,第 18 行的語句使用下面任何一個都是等效的:
- var b = a.AsObject();
- var b = (object)a;
- object b = a;
在 Arch Linux 操作系統的 Mono 環境中編譯和運行:
work$ dmcs Tester.cs && mono Tester.exe Thing Object
上述運行結果解釋如下:
- 第 19 行調用了定義在第 5 行的 GetId 實例方法,輸出: Thing 。
- 第 20 行調用了定義在第 10 行的 GetId 擴展方法,輸出: Object 。
使用 monodis 反匯編 Tester.exe,得到 Main 方法的 MSIL 代碼如下所示:
1 .namespace Skyiv.Test 2 { 3 .class private auto ansi abstract sealed beforefieldinit Tester extends [mscorlib]System.Object 4 { 5 // method line 5 6 .method private static hidebysig default void Main () cil managed 7 { 8 // Method begins at RVA 0x2108 9 .entrypoint 10 // Code size 36 (0x24) 11 .maxstack 7 12 .locals init ( 13 class Skyiv.Test.Thing V_0, object V_1) 14 IL_0000: newobj instance void class Skyiv.Test.Thing::'.ctor'() 15 IL_0005: stloc.0 16 IL_0006: ldloc.0 17 IL_0007: call object class Skyiv.Test.Extensions::AsObject(object) 18 IL_000c: stloc.1 19 IL_000d: ldloc.0 20 IL_000e: callvirt instance string class Skyiv.Test.Thing::GetId() 21 IL_0013: call void class [mscorlib]System.Console::WriteLine(string) 22 IL_0018: ldloc.1 23 IL_0019: call string class Skyiv.Test.Extensions::GetId(object) 24 IL_001e: call void class [mscorlib]System.Console::WriteLine(string) 25 IL_0023: ret 26 } // end of method Tester::Main 27 } // end of class Skyiv.Test.Tester 28 }
上述 MSIL 代碼中:
- 第 20 行對應 C# 程序第 19 行的 GetId 實例方法調用 (使用 callvirt 指令)。
- 第 23 行對應 C# 程序第 20 行的 GetId 擴展方法調用 (使用 call 指令)。