Linq系列—linq语法和扩展方法关系

转载至NET深入解析LINQ框架(四:IQueryable、IQueryProvider接口详解)

我们知道LINQ所支持的查询范围主要在IEnumerable、IQueryable这两个方面,对于我们想要扩展LINQ的查询能力也主要集中在这两块。很多时候我们在编写应用框架的时候,都会自己去实现IEnumerble对象,一般不会用系统提供的集合类,这样为了框架的OO性,上下文连贯性,更模型化。如果应用框架具备一定的查询能力是不是很方便些。比如你在开发一个关于数据密集性的框架,可能不是实时的持久化,但是能在外部提供某种查询工具来查询内存中的数据,所以这个时候需要我们能扩展LINQ的Object查询能力。这一节我们就来学习怎么扩展Linq to Object。
LINQ查询Object是基于IEnumerable对象的,不是集合对象有什么好查的。对于IEnumerable对象的LINQ查询是Enumerable静态对象在支撑着,然后通过匿名表达式来表示逻辑,这样就能顺其自然的查询集合。那么我们该如何下手扩展Linq to Object?其实也就是两点可以扩展,要么提供扩展方法来扩展IEnumerable对象,当然你别企图想让VS支持某种关键字让你对应扩展方法。还有就是继承IEnumerable对象让我们自己的集合类型具备LINQ的强类型的查询能力。当然具体要看我们需求,从技术角度看目前只有这两点可以扩展。
如果我们使用扩展方法那么只能是扩展IEnumerable对象,这没有问题。我们可以很方便的在LINQ的表达式中调用我们自己的扩展方法,让自己的方法跟着一起链式查询。如果我们从继承IEnumerable对象扩展,那么情况会有点小复杂,你的扩展方法中要扩展的对象一定要具体的给出对象的定义才行,如果你扩展的对象不能和继承的对象保持一直,那么你将断掉所有的扩展方法。[王清培版权所有,转载请给出署名]
2.1】.通过添加IEnumerable对象的扩展方法
下面我们通过具体的例子来分析一下上面的理论,先看看通过扩展方法来扩展系统的IEnumerable对象。
代码段:Order类
///
/// 订单类型
///
public class Order
{
///
/// 订单名称
///
public string OrderName { get; set; }
///
/// 下单时间
///
public DateTime OrderTime { get; set; }
///
/// 订单编号
///
public Guid OrderCode { get; set; }
}

这是个订单类纯粹是为了演示而用,里面有三个属性分别是”OrderName(订单名称)”、”OrderTime(下单时间)”、”OrderCode(订单编号)”,后面我们将通过这三个属性来配合示例的完成。
如果我们是直接使用系统提供的IEnumerable对象的话,只需要构建IEnumerable对象的扩展方法就能实现对集合类型的扩展。我假设使用List来保存一批订单的信息,但是根据业务逻辑需要我们要通过提供一套独立的扩展方法来支持对订单集合数据的处理。这一套独立的扩展方法会跟随着当前系统部署,不作为公共的开发框架的一部分。这样很方便也很灵活,完全可以替代分层架构中的部分Service层、BLL层的逻辑代码段,看上去更为优雅。
再发散一下思维,我们甚至可以在扩展方法中做很多文章,把扩展方法纳入系统架构分析中去,采用扩展方法封装流线型的处理逻辑,对业务的碎片化处理、验证的链式处理都是很不错的。只有这样才能真正的让这种技术深入人心,才能在实际的系统开发当中去灵活的运用。
下面我们来构建一个简单的IEnumerable扩展方法,用来处理当前集合中的数据是否可以进行数据的插入操作。
代码段:OrderCollectionExtent静态类
public static class OrderCollectionExtent
{
public static bool WhereOrderListAdd(this IEnumerable IEnumerable) where T : Order
{
foreach (var item in IEnumerable)
{
if (item.OrderCode != null && !String.IsNullOrEmpty(item.OrderName) && item.OrderTime != null)
{
continue;
}
return false;
}
return true;
}
}

OrderCollectionExtent是个简单的扩展方法类,该类只有一个WhereOrderListAdd方法,该方法是判断当前集合中的Order对象是否都满足了插入条件,条件判断不是重点,仅仅满足例子的需要。这个方法需要加上Order类型泛型约束才行,这样该扩展方法才不会被其他的类型所使用。
List orderlist = new List()
{
new Order(){ OrderCode=Guid.NewGuid(), OrderName=”水果”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(), OrderName=”办公用品”,OrderTime=DateTime.Now}
};
if (orderlist.WhereOrderListAdd())
{
//执行插入
}

如果.NET支持扩展属性【不过微软后期肯定是会支持属性扩展的】,就不会使用方法来做类似的判断了。这样我们是不是很优雅的执行了以前BLL层处理的逻辑判断了,而且这部分的扩展方法是可以动态的更改的,完全可以建立在一个独立的程序集当中。顺便在扩展点使用思路,在目前MVVM模式中其实也可以将V中的很多界面逻辑封装在扩展方法中来减少VM中的耦合度和复杂度。包括现在的MVC都可以适当的采用扩展方法来达到更为便利的使用模式。
但是大部分情况下我们都是针对所有的IEnunerale类型进行扩展的,这样可以很好的结合Linq的链式编程。原理就这么多,根据具体项目需要适当的采纳。[王清培版权所有,转载请给出署名]
2.2】.通过继承IEnumerable接口
我想大部分的情况下我们都是直接使用IEnumerable的实现类,但是在编写系统组件、框架的时候一般都是要自己去实现自己的迭代器类的。那么这个时候的扩展方法还能作用于我们继承下来的类,这是相当方便的,不知不觉我们自己扩展的组件将也会支持Linq的查询。但是这个时候应该适当的控制你针对继承下来的类的扩展,扩展方法应该是面向你内部使用的,不能污染到外部的对象。
我们继续看例子,该例子是针对继承IEnumerable来分析使用方式;
public class OrderCollection : IEnumerable
{
List orderList;
public OrderCollection()
{
orderList = new List() {
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”订单1”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”订单2”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”订单3”, OrderTime=DateTime.Now}
};
}

   public IEnumerator<Order> GetEnumerator()   
   {   
       foreach (var order in orderList)   
       {   
           yield return order;   
       }   
   }   
   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()   
   {   
       foreach (var order in orderList)   
       {   
           yield return order;   
       }   
   }   

}

这是个Order集合类型OrderCollection类,该类专门用来存放或处理Order类的。不管是从兼容.NET2.0或者其他方面考虑都可能将集合的类型封装在.NET2.0版本的程序集中,在.NET2.0之上的版本都会提供扩展版本的程序集,这个时候我们的扩展方法要专门针对OrderCollection去编写,否则就会造成 IEnumerable对象的污染。
public static OrderCollection GetOutOrderCollection(this OrderCollection OrderColl)
{
return OrderColl;
}
这个时候会很干净的使用着自己的扩展方法,不会造成大面积的污染。当然一般都是依赖倒置原则都会有高层抽象,不会直接扩展实现类,这里只是简单的介绍。[王清培版权所有,转载请给出署名]
2.3】.详细的对象结构图
这个小结主要将IEnumerable及它的扩展方法包括Linq的查询进行一个完整的结构分析,将给出详细的对象结构导图。
对象静态模型、运行时导图:
这里写图片描述
上图中的关键部分就是i==10将被封装成表达式直接送入Where方法,而select后面的i也是表达式【(int i)=>i】,也将被送入Select方法,这里就不画出来了。顺着数字序号理解,IEnumerable是Linq to Object的数据源,而Enumerable静态类是专门用来扩展Linq查询表达式中的查询方法的,所以当我们编写Linq查询IEnumerable集合是,其实是在间接的调用这些扩展方法,只不过我们不需要那么繁琐的去编写Lambda表达式,由编辑器帮我们动态生成。
小结:本节主要讲解了Linq to Object的原理,其实主要的原理就是Lambda表达式传入到Enumerable扩展方法当中,然后形成链式操作。Linq 只是辅助我们快速查询的语言,并不是.NET或者C#的一部分,在任何.NET平台上的语言中都可以使用。下面我们将重点分析Linq to Provider,这样我们才能真正的对LINQ进行高级应用。[王清培版权所有,转载请给出署名]
3.】.实现IQueryable 、IQueryProvider接口
这篇文章的重点就是讲解IQueryable、IQueryProvider两个接口的,当我们搞懂了这两个接口之后,我们就可以发挥想象力的去实现任何一个数据源的查询。IQueryable、IQueryProvider两接口还是有很多值得我们研究的好东西,里面充斥大量的设计模式、数据结构的知识,下面我们就来慢慢的分析它的美。
IQueryable接口是Linq to Provider的入口,非常有意思的是它并不是一个IQueryable来支撑一次查询。我们在编写Linq语句的时候一般都是 where什么然后select 什么,至少连续两个扩展方法的映射调用,但是朋友你知道它内部是如何处理的吗?每当Where过后紧接着Select他们是如何关联一个完整的查询的?IQueryable并非IEnumerable对象,无法实时的做出处理然后将结果返回给下一个方法接着执行。那么它如何将片段性的执行方法串成一个整的、完整的查询?下面我们将逐个的分析这其中要涉及到的模式、数据结构、框架原则,这些搞懂了之后代码都是模型的表现,也就顺其自然的明白了。
3.1】.延迟加载IEnumertor对象(提高系统性能)
延迟加载的技术其实在Linq之前就已经在使用,只不过很少有人去关注它,都被隐藏在系统框架的底层。很多场合下我们需要自己去构建延迟加载特性的功能,在IEnumerable对象中构建延迟基本上是通过yield return 去构建一个状态机,当进行迭代的时候才进行数据的返回操作。那么在IQueryable中是通过执行Provider程序来获取数据,减少在一开始就获取数据的性能代价。IQueryable继承自IEnumerable接口,也就是可以被foreach语法调用的,但是在GetEnumerator方法中才会去执行提供程序的代码。我们来分析一下IQueryable接口的代码。
public IEnumerator GetEnumerator()
{
return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();
}

   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()   
   {   
       return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();   
   }  

这是IQueryable接口中从IEnumerable继承下来的两个返回IEnumerator接口类型的方法,在我们目前使用的Linq to Sql、Linq to Entity中都会返回强类型的集合对象,一般都不会实时的进行数据查询操作,如果要想实时执行需要进行IQueryable.Provider.Execute方法的直接调用。

我们用图来分析一下Linq to Provider中的延迟加载的原理;
这里写图片描述
这段代码不会被立即执行,我们跟踪一下各个组成部分之间的执行过程;
这里写图片描述
这幅图重点是IQueryable对象的连续操作,大致原理是每次执行扩展方法的时候都会构造一个新的IQueryable,本次的IQueryable对象将包含上次执行的表达式树,以此类推就形成了一颗庞大的表达式树。详细的原理在下面几小节中具体分析。[王清培版权所有,转载请给出署名]

最后Orderlist将是一个IQueryable类型的对象,该对象中包含了完整的表达式树,这个时候如果我们不进行任何的使用将不会触发数据的查询。这就是延迟加载的关键所在。如果想立即获取orderlist中的数据可以手动执行orderlist.Provider.Execute(orderlist.Expression)来获取数据。
3.2】.扩展方法的扩展对象之奥秘(this IQueryable source)
其实这里有一个思维陷阱,当我们分析源码的时候只将焦点集中在扩展方法中的后面参数上,而没有集中精力考虑扩展方法所扩展的对象本身,看似不同的方法位于不同的地方,其实他们来自一个地方,所在的逻辑对象是一个,但是这恰恰会造成我们分析问题的瓶颈,这里我们重点的讲解一下扩展方法所扩展对象。
我们直接用源码进行讲解吧;
public static IQueryable Select

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值