淺談 System.Linq.Enumerable.AsEnumerable 方法


引言

在 MSDN 中對 System.Linq.Enumerable 類的 AsEnumerable 方法相關描述如下所示:

Enumerable.AsEnumerable<TSource> 方法: 返回類型化為 IEnumerable<T> 的輸入。
命名空間: System.Linq
程序集: System.Core (在 System.Core.dll 中)
語法: public static IEnumerable AsEnumerable( this IEnumerable source)

備注:
除了將 source 的編譯時類型從實現 IEnumerable<T> 的類型更改為 IEnumerable<T> 本身之外,AsEnumerable<TSource>(IEnumerable<TSource>) 方法沒做其他任何事。
The AsEnumerable(IEnumerable) method has no effect other than to change the compile-time type of source from a type that implements IEnumerable to IEnumerable itself.

AsEnumerable<TSource>(IEnumerable<TSource>) 可用於在序列實現 IEnumerable<T> 時在查詢實現之間進行選擇,同時它還具有一組不同的可用公共查詢方法。例如,假設給定一個泛型類 Table,該類實現 IEnumerable<T> 並且具有自己的方法(如 Where、Select 和 SelectMany),則調用 Where 將調用 Table 的公共 Where 方法。表示數據庫表的 Table 類型可能具有一個 Where 方法,該方法將謂詞參數作為表達式目錄樹,並將該樹轉換為 SQL 以供遠程執行。如果不需要遠程執行(例如,謂詞調用本地方法),則 AsEnumerable 方法可用於隱藏自定義方法,並使標准查詢運算符變為可用。
AsEnumerable<TSource>(IEnumerable<TSource>) can be used to choose between query implementations when a sequence implements IEnumerable<T> but also has a different set of public query methods available.For example, given a generic class Table that implements IEnumerable<T> and has its own methods such as Where, Select, and SelectMany, a call to Where would invoke the public Where method of Table.A Table type that represents a database table could have a Where method that takes the predicate argument as an expression tree and converts the tree to SQL for remote execution.If remote execution is not desired, for example because the predicate invokes a local method, the AsEnumerable method can be used to hide the custom methods and instead make the standard query operators available.

實際上,這個方法只有一條語句,就是原樣返回它的唯一參數:

  • reutrn source

使用 .NET Reflector 查看 .NET Framework 4.5 Class Library,就很清楚了:

AsEnumerable

測試程序

這種只是原樣返回它的唯一參數的方法有什么用呢?讓我們來看一個例子吧:

 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 }

在上述程序中:

  1. Firster<T> 類是 List<T> 類的派生類。
  2. List<T> 類實現了 IEnumerable<T> 接口。
  3. IEnumerable<T> 接口有一個 First 擴展方法 (定義在 System.Linq.Enumerable 類中)。
  4. Firster<T> 類定義了一個 First 實例方法。

在 Arch Linux 的 Mono 環境下編譯和運行:

work$ dmcs LinqTester.cs && mono LinqTester.exe
F:0
L:2
I:2

上述運行結果解釋如下:

  1. 第 17 行調用第 10 行的 Test 方法,參數類型是 Firster<T>,於是調用 Firster<T> 類的 First 實例方法,輸出: F:0。
  2. 第 18 行調用第 11 行的 Test 方法,參數類型是 List<T>,在 List<T> 類中沒有找到 First 方法,由於 List<T> 類實現了 IEnumerable<T> 接口,所以調用 IEnumerable<T> 接口的 First 擴展方法,輸出: L:2。
  3. 第 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 }

在上述程序中:

  1. Thing 類定義了一個 GetId 實例方法。
  2. Extensions 類為 object 類定義了一個 GetId 擴展方法。
  3. 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

上述運行結果解釋如下:

  1. 第 19 行調用了定義在第 5 行的 GetId 實例方法,輸出: Thing 。
  2. 第 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 代碼中:

  1. 第 20 行對應 C# 程序第 19 行的 GetId 實例方法調用 (使用 callvirt 指令)。
  2. 第 23 行對應 C# 程序第 20 行的 GetId 擴展方法調用 (使用 call 指令)。

參考資料

  1. MSDN: (System.Linq) Enumerable.AsEnumerable<TSource> 方法
  2. MSDN: (System.Linq) Enumerable.First<TSource> 方法
  3. MSDN: (System.Linq) Enumerable 類
  4. MSDN: (System.Collections.Generic) List<T> 類


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM