分享

LINQ探秘:实现一个自己的LINQ Provider - .NET技术 / C#

 orion360doc 2011-04-27
C#添加了一项重要的特性,就是 LINQ。

借助LINQ,我们可以用查询语法来检索对象。

C# code
                        
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { List<string> list = new List<string>() { "Hello", "World", "ABC", "China" }; (from x in list where x.Length > 4 select x).ToList().ForEach(x => Console.WriteLine(x)); } } }


这样一段代码,可以筛选出字符串列表里面长度大于4的字符。

但是 LINQ 是怎么工作的呢?

其实 LINQ就是调用了 System.Linq 下的一组扩展方法。

C# code
                        
(from x in list where x.Length > 4 select x).ToList().ForEach(x => Console.WriteLine(x));


等价于:
 
C# code
                        
list.Where(x => x.Length > 4 select x).Select(x => x).ToList().ForEach(x => Console.WriteLine(x));


我们注释掉 using System.Linq;。

程序无法编译了。。。

下面我们用自己的代码实现 Where、Select和ToList:

添加如下代码:
C# code
                        
static class MyLinq { public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { foreach (var item in source) { if (predicate(item)) yield return item; } } public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) { foreach (var item in source) { yield return selector(item); } } public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { List<TSource> list = new List<TSource>(); foreach (var item in source) list.Add(item); return list; } }


编译,成功!

但是真的是我们的代码起作用了么?

我们修改下:
C# code
                        
if (predicate(item)) yield return item;

我们修改成
C# code
                        
if (!predicate(item)) yield return item;


再次运行,我们发现过滤反过来了!

赶快改回来。

上面的内容只是热身一下,下面重点来了:

如何实现一个自己的LINQ Provider。

我觉得在讨论这个问题之前,有必要说下为什么我们要实现自己的 Provider。其实很简单:LINQ是基于对象的,有时候我们需要返回来自远程的数据,如果返回所有的数据,再过滤,就浪费了大量的网络带宽。

事实上 Linq to SQL 就是这样——如果没有 Linq To SQL,我们需要从数据库返回所有的数据,然后本地筛选,听听就是多么恐怖的事!!!
Linq To SQL 的原理是,将 LINQ 表达式转换为 SQL,然后再查询和返回数据。

我们的例子围绕这样一个假想的情况展开:我们需要从人人网上获取新鲜事,人人提供了API,我们可以返回某个人的新鲜事。

为了简化起见,例子没有真正从renren获取数据,而是模拟了这个过程。

下面是源程序:

C# code
                        
using System; using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { (from x in new RenrenFeeds() where x.FeedOwner == "张三" select x).ToList().ForEach(x => Console.WriteLine(x)); } } class RenrenFeed { public string FeedOwner { get; set; } public string FeedContent { get; set; } public string URL { get; set; } public override string ToString() { return FeedContent; } } class RenrenFeeds : IQueryable<RenrenFeed>, IQueryProvider { private Expression _expression = null; private IList<RenrenFeed> _feeds = new List<RenrenFeed>(); //在真实的环境里面,我们传入不同的Owner,返回指定的数据 private IList<RenrenFeed> GetFeeds(string Owner = "") { List<RenrenFeed> list = new List<RenrenFeed> { new RenrenFeed() { FeedOwner = "张三", FeedContent = "张三分享了一张图片", URL = "http:///photo/zhangsan" }, new RenrenFeed() { FeedOwner = "张三", FeedContent = "张三撰写了一篇日志:如何使用LINQ", URL = "http:///blog/zhangsan" }, new RenrenFeed() { FeedOwner = "李四", FeedContent = "李四分享了一张图片", URL = "http:///photo/lisi" }, new RenrenFeed() { FeedOwner = "李四", FeedContent = "李四撰写了一篇日志:C#学习笔记", URL = "http:///blog/lisi" } }; if (Owner != "") return list.Where(x => x.FeedOwner == Owner).ToList(); else return list; } public IEnumerator<RenrenFeed> GetEnumerator() { return (this as IQueryable).Provider.Execute<IEnumerator<RenrenFeed>>(_expression); } IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator<RenrenFeed>)(this as IQueryable).GetEnumerator(); } public Type ElementType { get { return typeof(RenrenFeed); } } public Expression Expression { get { return Expression.Constant(this); } } public IQueryProvider Provider { get { return this; } } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { if (typeof(TElement) != typeof(RenrenFeed)) throw new Exception("不能查询RenrenFeed以外的类型!"); _expression = expression; return (IQueryable<TElement>)this; } public IQueryable CreateQuery(Expression expression) { return (IQueryable<RenrenFeed>)(this as IQueryProvider).CreateQuery<RenrenFeed>(expression); } public TResult Execute<TResult>(Expression expression) { MethodCallExpression methodcall = _expression as MethodCallExpression; //这里没有完成,我们需要解析条件参数,并且转换成对GetFeeds的正确调用。 foreach (var param in methodcall.Arguments) { if (param.NodeType == ExpressionType.Quote) { LambdaExpression l = Expression.Lambda(Expression.Convert(param, param.Type)); LambdaExpression l1 = (LambdaExpression)l.Compile().DynamicInvoke(); Func<RenrenFeed, bool> func = (Func<RenrenFeed, bool>)l1.Compile(); _feeds = GetFeeds().Where(x => func(x)).ToList(); } Console.WriteLine(param.ToString()); // 我们可以看到 where 产生的 lambda } return (TResult)_feeds.GetEnumerator(); } public object Execute(Expression expression) { return (this as IQueryProvider).Execute<IEnumerator<RenrenFeed>>(expression); } } }



很抱歉,这东西实在太复杂,我只写了个大概。

有熟悉表达式API的牛人可以完善。
#14楼 得分:0回复于:2011-04-26 08:17:55
感谢LZ的无私分享


引用楼主 caozhy 的回复:
list.Where(x => x.Length > 4 select x).Select(x => x).ToList().ForEach(x => Console.WriteLine(x));

list.Where(x => x.Length > 4).ToList().ForEach(x => Console.WriteLine(x));
#17楼 得分:0回复于:2011-04-26 08:36:52
表达式树不是太好理解
不过做熟悉的话也不会很难
#22楼 得分:0回复于:2011-04-26 09:59:26
这个以前没见到过诶
  • dongxinxi用户头像
  • dongxinxi
  • (人生天地间,忽如远行客)
  • 等 级:
#32楼 得分:0回复于:2011-04-26 10:44:13
先顶再细看
  • dongxinxi用户头像
  • dongxinxi
  • (人生天地间,忽如远行客)
  • 等 级:
#35楼 得分:0回复于:2011-04-26 10:50:27
那个前天就碰到一个问题,想像SQL那样动态构造where可选条件,时间关系临时用三目应赴过去了。
后来一想,如果条件再复杂点,三目就力不从心了,或者看着让人恶心。
最后才想起之前看到过的动态构造LINQ表达式树
谢LZ分享
#36楼 得分:0回复于:2011-04-26 10:50:53
看不懂核心部分
  • caozhy用户头像
  • caozhy
  • (小学毕业,自学成才)
  • 等 级:
#37楼 得分:0回复于:2011-04-26 11:11:17
引用 35 楼 dongxinxi 的回复:
那个前天就碰到一个问题,想像SQL那样动态构造where可选条件,时间关系临时用三目应赴过去了。
后来一想,如果条件再复杂点,三目就力不从心了,或者看着让人恶心。
最后才想起之前看到过的动态构造LINQ表达式树
谢LZ分享

对于表达式API和表达式树,我没有什么研究。觉得还是晦涩了一些,而且下一个版本的C#会简化动态编程模型的。
#44楼 得分:0回复于:2011-04-26 11:59:46
引用 35 楼 dongxinxi 的回复:

那个前天就碰到一个问题,想像SQL那样动态构造where可选条件,时间关系临时用三目应赴过去了。
后来一想,如果条件再复杂点,三目就力不从心了,或者看着让人恶心。
最后才想起之前看到过的动态构造LINQ表达式树
谢LZ分享

还有个解决方案:
当逻辑判断比较复杂时 可抽出来写成扩展方法
  • dongxinxi用户头像
  • dongxinxi
  • (人生天地间,忽如远行客)
  • 等 级:
#47楼 得分:0回复于:2011-04-26 12:03:58
引用 44 楼 q107770540 的回复:
引用 35 楼 dongxinxi 的回复:

那个前天就碰到一个问题,想像SQL那样动态构造where可选条件,时间关系临时用三目应赴过去了。
后来一想,如果条件再复杂点,三目就力不从心了,或者看着让人恶心。
最后才想起之前看到过的动态构造LINQ表达式树
谢LZ分享

还有个解决方案:
当逻辑判断比较复杂时 可抽出来写成扩展方法

的确是个好办法
引用 37 楼 caozhy 的回复:
对于表达式API和表达式树,我没有什么研究。觉得还是晦涩了一些,而且下一个版本的C#会简化动态编程模型的。

太过谦虚了,记得在另外一篇帖子里LZ提到的DSL领域语言,需求完了程序就随之出来了,的确很难想象。
CLR已经加入了越来越多动态语言特性,程序语法对需求逻辑的实现越来越像自然语言(虽然动态会损失那么点点性能)
#58楼 得分:0回复于:2011-04-27 10:10:18
复杂的做成扩展方法是好主意。个人觉得Linq to Sql将表达式转成sql真的是太复杂了,毕竟linq to XML linq to json这种转换都容易的多
  • asdf311用户头像
  • asdf311
  • (dark wanderer)
  • 等 级:
#61楼 得分:0回复于:2011-04-27 16:27:19
感谢LZ的分享,不错的思路哈,很有启发

C# code
                        
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { if (typeof(TElement) != typeof(RenrenFeed)) throw new Exception("不能查询RenrenFeed以外的类型!"); _expression = expression; return (IQueryable<TElement>)this; } 可修改为: public IQueryable<TElement> CreateQuery<TElement>(Expression expression) where TElement : RenrenFeed { _expression = expression; return this as IQueryable<TElement>; }


可以直接使用泛型的where约束,这样直接就是编译错误
还有,个人认为,这里还是用as好点,RenrenFeed应该不会涉及用户自定义类型转换

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多