浅谈 System.Linq.Enumerable.AsEnumerable 方法

本文详细解析了MSDN中关于Enumerable.AsEnumerable方法的描述及其在不同场景下的应用,通过具体代码示例展示了如何利用此方法在查询实现之间进行选择,以及在隐藏自定义方法与使用标准查询运算符之间的权衡。文章最后提供了两个测试程序,用于直观演示方法的不同用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

在 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> 类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值