银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::
  268 随笔 :: 2 文章 :: 2605 评论 :: 48 引用

公告

引言

在 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> 类
posted on 2013-01-20 16:39 银河 阅读(...) 评论(...) 编辑 收藏