当前位置 : 主页 > 编程语言 > 其它开发 >

不继承 IEnumerable 或 IQueryable 的类型怎么使用 LINQ 查询

来源:互联网 收集:自由互联 发布时间:2022-05-25
最近想研究如何自定义 LINQ Provider ,但是一直无法入手,先写点收获吧~ MSDN 上的这篇文章( 《启用数据源以进行 LINQ 查询》 )中写到: 如果想对自己的数据源进行 LINQ 查询,那必须使

最近想研究如何自定义 LINQ Provider ,但是一直无法入手,先写点收获吧~

MSDN 上的这篇文章(《启用数据源以进行 LINQ 查询》)中写到:

如果想对自己的数据源进行 LINQ 查询,那必须使用一下四种方法的其中一种。

  1. 实现 IEnumerable<T> 接口
  2. 实现标准的查询方法
  3. 实现 IQueryable<T> 接口
  4. 扩展已经实现的 LINQ 查询

看到其中第二条,让人心生疑惑,那下面就来探讨一下吧~

 

什么类型可以进行 LINQ 查询? 1 2 3 var queryLondonCustomers = from cust in customers                            where cust.City == "London"                            select cust;

很简单的几行代码,这就是 LINQ,这里有几个关键字: frominwhereselect …

这几个是新增的关键字,还有别的好多个~

关键看这里的 customers ,这里就是一个可以被 LINQ 查询的对象。

 

按照一般的思路,它一定是继承了某个接口,所以可以用 LINQ 查询。

在学习 LINQ 的时候我们常常会看到两个接口:IEnumerable<T> 和 IQueryable<T> ,难道是这两个?

备注:这两个接口的区别是,前者提供的是对内存中数据的查询,后者提供的是对远程数据的查询,这里就不展开了。

 

如果你没有深入了解,恐怕你就会认为的确如此了。

这里有两个疑点:

  1. 如果你说是 IQueryable<T>,那还可以理解,因为这是一个新的接口,但是为什么 IEnumerable<T> 也可以呢?这个可不是一个新接口哦,一般情况下,新版本的 .net 怎么可能修改以前的接口呢?
  2. 如果真是如此,那在 MSDN 的文章中为什么说就算不集成这两个接口,只要实现了基本的查询语句就可以实现 LINQ 查询呢?

让我们继续探讨吧!

 

反编译看IL代码

一段代码

1 2 3 4 5 6 7 8 9 10 class Program {     static void Main(string[] args)     {         var data = Enumerable.Range(1, 5).ToArray();         var q = from _ in data                 where _ > 3                 select _;     } }

 

让我们看看它的 IL 代码(部分)

1 2 3 4 IL_0029: ldsfld class [mscorlib]System.Func`2<int32, bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate1' IL_002e: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>) IL_0033: stloc.1 IL_0034: ret

 

关键代码:[System.Core]System.Linq.Enumerable::Where<int32>

让我们找到它的出处

1 2 3 4 5 6 7 8 9 10 11 12 13 14 namespace System.Linq {     public static class Enumerable     {         public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {             if (source == null) throw Error.ArgumentNull("source");             if (predicate == null) throw Error.ArgumentNull("predicate");             if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);             if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);             if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);             return new WhereEnumerableIterator<TSource>(source, predicate);         }     } }

 

其实,它最终只是调用了 IEnumerable<T> 的一个扩展方法,难怪别人总说上面的代码其实等效于这个

1 2 3 4 5 var data = Enumerable.Range(1, 5).ToArray(); var q = from _ in data         where _ > 3         select _; var q2 = data.Where(_ => _ > 3);//等效于上面一行代码

 

让我们来理一下思路:

  • LINQ 查询里的 where 关键字等效于调用 Where 这个方法(别的关键字也是如此);
  • 这个 Where 方法不在接口内,而是在针对这个接口的扩展方法内;
  • LINQ 查询对接口貌似没有约束,仅仅是看你是否有这个方法(目前仅仅是猜测)。

 

为了验证上面的是否正确,那就让我们来动手实践一下吧!

 

为自定义类型提供 LINQ 查询

按照刚才分析的,是不是只要给自己的类型提供几个方法就行了呢?

动手吧!

我们先来实现 select 和 where 关键字吧~

先看看 .net 源码中的函数吧,这样我们才能知道这个函数需要传入什么,需要返回什么。

1 2 3 4 5 6 7 8 9 10 namespace System.Linq {     public static class Enumerable     {         public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {         }         public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) {         }     } }

这里不用看具体的实现,只要看传入的参数和返回类型就行了。

那么接下来就让我们写自己的类型吧!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Program {     static void Main(string[] args)     {         var mySchool = new School();         var q = from _ in mySchool                 where _.Contains("a")                 select _;     } } class School {     protected List<string> Student { get; set; }     public School()     {         //生成一个 Student 序列         Student = new List<string>                         {                             "abc",                             "xyz",                             "123"                         };     }     public School Where(Func<string, bool> predicate)     {         //这里对List有增删了,所以不能直接用foreach,删除不满足条件的学生         for (var k = 0; k < Student.Count; k++)         {             if (predicate(Student[k])) continue;             Student.RemoveAt(k);             k--;         }         return this;     }     public School Select(Func<School, School> selector)     {         //这里就不实现了         return this;     } }

这里的场景:

School 类型,并不是什么枚举类型,但是需要用 where 做一些过滤,用 order 做一些排序,里面的数据呢,是不暴露出来的。

所以如果不继承 IEnumerable<T> 或 IQueryable<T> 的接口话,只要模仿它们的扩展方法,实现一些基本的方法即可。

具体的实现貌似没找到什么好的资料,只能自己慢慢摸索了~ 这其中还有一些泛型,可以根据自己的需求做相应的修改!

 

反思

从上面的种种迹象表明,LINQ 应该是编译器级别的东西,而不是 .net CLR 级的东西(这个仅仅是个人猜测)

写完了这些,我一直在想微软为什么要真么做?

按照一般的设计思路,理想状态下,应该有一个统一的接口来约束,然后所有继承这个接口的类型都可以进行 LINQ 查询。

但是为什么没这么做呢?

 

个人分析:

微软想要实现 Ling to everything,所以要让一切数据支持 LINQ,一般可以认为实现 IEnumerable<T> 接口的类型都是数据,也就是说要让所有实现 IEnumerable<T> 接口的类型都可以实现 LINQ 查询。

但是这是一个接口,不是一个抽象类,不能写具体的方法。LINQ 查询只要用的 IEnumerable<T> 接口所提供的函数就行了。

所以扩展方法是一个非常明智的选择!

 

那 IQueryable<T> 是怎么回事呢?IQueryable<T> 中包含一个 IQueryProvider 类型,它可以将本地的查询转换为远程的查询,忽略内部实现,你可以认为 IEnumerable<T> 和 IQueryable<T>是一样的。

 

不继承 IEnumerable<T> 的就不是数据了吗?恐怕不是吧~

不是说 Ling to everything 吗?为什么不对 Object 写 LINQ 查询呢?

首先不是所有的 Object 都是数据,另外 Object 也没有可以依赖的方法供数据查询用。

 

所以如果是你自己的类型,并且不实现 IEnumerable<T> 或 IQueryable<T> 接口,那么你只要自己写一些函数就行了!

而且可以自由地控制,例如:如果排序方法对你的类型来说没有意义,那你可以不写它。

虽然我还是觉得这样的设计略微有点不严谨,但它的确很方便~

文章同时发布在了我的个人博客:不继承 IEnumerable 或 IQueryable 的类型怎么使用 LINQ 查询

知识共享许可协议本作品采用知识共享Attribution 2.5 China Mainland许可协议进行许可。
欢迎转载,演绎或用于商业目的,但是必须保留本文的署名Dozer
上一篇:通过WCF扩展实现消息压缩
下一篇:没有了
网友评论