您的位置:首页 > 其它

.NET深入解析LINQ框架(一:LINQ优雅的前奏)[转载]

2013-07-29 15:29 369 查看

.NET深入解析LINQ框架(一:LINQ优雅的前奏)

阅读目录:

1.LINQ简述

2.LINQ优雅前奏的音符

2.1.隐式类型 (由编辑器自动根据表达式推断出对象的最终类型)

2.2.对象初始化器 (简化了对象的创建及初始化的过程)

2.3.Lambda表达式 (对匿名方法的改进,加入了委托签名的类型推断并很好的与表达式树的结合)

2.4.扩展方法 (允许在不修改类型的内部代码的情况下为类型添加独立的行为)

2.5.匿名类型 (由对象初始化器推断得出的类型,该类型在编译后自动创建)

2.6.表达式目录树(用数据结构表示程序逻辑代码)

3.LINQ框架的主要设计模型

3.1.链式设计模式(以流水线般的链接方式设计系统逻辑)

3.2.链式查询方法(逐步加工查询表达式中的每一个工作点)

4.LINQ框架的核心设计原理

4.1.托管语言之上的语言(LINQ查询表达式)

4.2.托管语言构造的基础(LINQ依附通用接口与查询操作符对应的方法对接)

4.3.深入IEnumerable、IEnumerable<T>、Enumerable(LINQ to Object框架的入口)

4.4.深入IQueryable、IQueryable<T>、Queryable(LINQ to Provider框架的入口)

4.5.LINQ针对不同数据源的查询接口

5.动态LINQ查询(动态构建Expression<T>表达式树)

6.DLR动态语言运行时(基于CLR之上的动态语言运行时)

1】.LINQ简述

LINQ简称语言集成查询,设计的目的是为了解决在.NET平台上进行统一的数据查询。

微软最初的设计目的是为了解决对象/关系映射的解决方案,通过简单的使用类似T-SQL的语法进行数据实体的查询和操作。不过好的东西最终都能良性的发展演化,变成了如今.NET平台上强大的统一数据源查询接口。

我们可以使用LINQ查询内存中的对象(LINQ to Object)、数据库(LINQ to SQL)、XML文档(LINQ to XML),还有更多的自定义数据源。

使用LINQ查询自定义的数据源需要借助LINQ框架为我们提供的IQueryable、IQueryProvider两个重量级接口。后面的文章将讲解到,这里先了解一下。

在 LINQ未出现之前,我们需要掌握很多针对不同数据源查询的接口技术,对于OBJECT集合我们需要进行重复而枯燥的循环迭代。对于数据库我们需要使用诸 多T-SQL\PL-SQL之类的数据库查询语言。对于XML我们需要使用XMLDOM编程接口或者XPATH之类的东西,需要我们掌握的东西太多太多, 即费力又容易忘。

那么LINQ是如何做到对不同的数据源进行统一的访问呢?它的优雅不是一天两天就修来的,归根到底还得感谢C#的设计师们,是他们让C#能如此完美的演变,最终造就LINQ的优雅。

下面我们来通过观察C#的每一次演化,到底在哪里造就了LINQ的优雅前奏。

2】.LINQ优雅前奏的音符

2.1.隐式类型(由编辑器自动根据表达式推断出对象的最终类型)


隐式类型其实是编辑器玩的语法糖而已,但是它在很大程度上方便了我们编码。熟悉JS的朋友对隐式类型不会陌生,但是JS中的隐式类型与这里的C#隐式类型是有很大区别的。尽管在语法上是一样的都是通过var关键字进行定义,但是彼此最终的运行效果是截然不同。

JS是基于动态类型系统设计原理设计的,而C#是基于静态类型系统设计的,两者在设计原理上就不一样,到最后的运行时更不同。

这里顺便推荐一本C#方面比较深入的书籍 《深入解析C#》,想深入学习C#的朋友可以看看。这书有两版,第二版是我们熟悉的姚琪琳大哥翻译的很不错。借此谢谢姚哥为我们翻译这么好的一本书。这本 书很详细的讲解了C#的发展史,包括很多设计的历史渊源。来自大师的手笔,非常具有学习参考价值,不可多得的好书。

我们通过一个简短的小示例来快速的结束本小节。



int[] Number = new int[5] { 1, 2, 3, 4, 5 };
var result = Filter(Number, (int item) => { return item > 3; });


我将上面的代码修改成了不需要显示指定泛型类型实参调用,这里也是可以的。

我们在定义 Filter<T>泛型方法时将Func<T,bool>泛型委托中的T定义为匿名函数的参数类型,所以在我们使用的时候需要指 定出类型实参(int item)中的item来表示委托将要使用的类型参数形参。在编辑器看来我们在定义泛型方法Filter时所用的泛型占位符T也恰巧是Filter方法的 形参数据类型Func<T,bool>中使用的调用参数类型,所以这里的语法分析规则能准确的推断出我们使用的同一种泛型类型实参。(这里要记住目前IDE编辑器只支持方法调用的泛型类型推断,也就是说其他方面的泛型使用是不支持隐式的类型推断,还是需要我们手动加上类型实参。)

这里顺便提一下关于延迟加载技术,延迟加载技术在集合类遍历非常有用,尤其是在LINQ中。很多时候我们对集合的处理不是实时的,也就是说我获取集合的数据不是一次性的,需要在我需要具体的某一个项的时候才让我去处理关于获取的代码。我稍微的改动了一下Filter代码:

public static List<Order> GetOrderList()
{
return new List<Order>();
}


有参数方法:

public static List<Order> GetOrderListByModel(Order model)
{
return new List<Order>();
}


Order对象只是一个类型,这里没有什么特别意义。

两个带有Func委托的方法,用来演示泛型的类型推断:

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

这里的问题是,如果我使用 GetOrderList方法作为GetModelList<TResult>(Func<TResult> GetFunc)泛型方法的参数是没有任何问题的,编辑器能真确的推断出泛型的类型。但是如果我使用GetOrderListByModel作为 GetModelList<TSource, TResult>(Func<TSource, TResult> GetFunc)重载版本的泛型方法时就不能真确的推断出类型。其实这里的Func中的TResult已经是方法的返回类型,TSource也是方法的参 数类型,按道理是完全可以进行类型推断的。可是我尝试了很多种方式就是过不起。奇怪的是如果我使用带有参数和返回类型的Lambda表达式作为 GetModelList<TSource, TResult>(Func<TSource, TResult> GetFunc)方法的参数时就能正确的类型推断。

方法调用的图例:

OrderCollection orderCollection = new OrderCollection();
orderCollection.Count();


还有一个需要大家注意的是,如果我们 定义的扩展方法在另外的命名空间里,我们在使用的时候一定要在当前的CS代码中应用扩展方法所在的命名空间,要不然编辑器是不会去寻找你目前在使用的对象 的扩展方法的,切忌。这里还有一点是需要我们注意的,当我们在设计后期可能会被扩展方法使用的对象时需要谨慎的考虑对象成员访问权限,如果我们将以后可能 会被扩展方法使用的对象设计成受保护的或者私有的,那么可能会涉及到无法最大力度的控制。

2.5.匿名类型(由对象初始化器推断得出的类型,该类型在编译后自动创建)

匿名类型其实也是比较好理解的,顾名思义匿名类型是没有类型定义的类型。这种类型是由编辑器自动生成的,仅限于当前上下文使用。废话少说了,我们还是看例子吧;

var Student1 = new { Name = "王清培", Age = 24, Sex = "男", Address = "江苏淮安" };
var Student2 = new { Name = "陈玉和", Age = 23, Sex = "女", Address = "江苏盐城" };


定义匿名类型跟普通的定义类型差不多,只不过在new之后是一对大括号,然后经跟着你需要使用到的属性名称和值。

匿名类型的作用域;

匿名类型在使用上是有它先天性缺点 的,由于缺乏显示的类型定义,所以无法在方法之间传递匿名类型。要想获取匿名类型的各属性值只能通过反射的方式动态的获取运行时的属性对象,然后通过属性 对象去获取到属性的值。匿名类型在使用的时候才会被创建类型,所以它在运行时存在着完整的对象定义元数据,所以通过反射获取数据是完全可以理解的。

下面我们使用上面定义的类型来获取它的各个属性。

Func<int> Func = () => 10;
Expression<Func<int>> Expression = () => 10;


编辑器对上述两行代码各采用了不同的处理方式,请看跟踪对象状态。



不使用Expression<T>作为委托类型的包装的话,该类型将是普通的委托类型。



如果使用了 Expression<T>作为委托类型的包装的话,编译器将把它解析成继承自 System.Linq.Expression.LambdaExpression类型的对象。一旦变成对象,那么一切就好办多了,我们可以通过很简单的 方式获取到Expression内部的数据结构。

表达式目录树的对象模型;

上面简单的介绍了一下表达式目录树的用意和基本的原理,那么表达式目录树的继承关系或者说它的对象模型是什么样子的?我们只有理清了它的整体结构这样才能方便我们以后对它进行使用和扩展。

下面我们来分析一下它的内部结构。

(Student stu)=>stu.Name=="王清培",我定义了一个Lambda表达式,我们可以视它为一个整体的表达式。什么叫整体的表达式,就是说完全 可以用一个表达式对象来表示它,这里就是我们的LambdaExpression对象。表达式目录树的本质是用对象来表达代码的逻辑结构,那么对于一个完 整的Lambda表达式我们必须能够将它完全的拆开才能够进行分析,那么可以将Lambda表达式拆分成两部分,然后再分别对上一次拆开的两部分继续拆 分,这样递归的拆下去就自然而然的形成一颗表达式目录树,其实也就是数据结构里面的树形结构。那么在C#里面我们很容易的构造出一个树形结构,而且这颗树 充满着多态。

(Student stu)=>stu.Name="王清培",是一个什么样子的树形结构呢?我们来看一下它的运行时树形结构,然后在展开抽象的继承图看一下它是如何构造出来的。



上图中的第一个对象是 Expression<T>泛型对象,通过跟踪信息可以看出,Expression<T>对象继承自 LambdaExpression对象,而LambdaExpression对象又继承自Expression抽象类,而在抽象里重写了ToString 方法,所以我们在看到的时候是ToString之后的字符串表示形式。

Lambda表达式对象主要有两部分 组成,从左向右依次是参数和逻辑主题,也就对应着Parameters和Body两个公开属性。在Parameters是所有参数的自读列表,使用的是 System.Collection.ObjectModel.ReadOnlyCollection<T>泛型对象来存储。

这里也许你已经参数疑问,貌似表达式 目录树的构建真的很完美,每个细节都有指定的对象来表示。不错,在.NET3.5框架中引入了很多用来表示表达式树逻辑节点的对象。这些对象都是直接或间 接的继承自Expression抽象类,该类表示抽象的表达式节点。我们都知道表达式节点各种各样,需要具体化后才能直接使用。所以在基类 Expression中只有两个属性,一个是public ExpressionType NodeType { get; },表示当前表达式节点的类型,还有另外一个public Type Type { get; },表示当前表达式的静态类型。何为静态类型,就是说当没有变成表达式目录树的时候是什么类型,具体点讲也就是委托类型。因为在委托类型被 Expression<T>泛型包装后,编译器是把它自动的编译成表达式树的数据结构类型,所以这里需要保存下当前节点的真实类型以备将来使 用。

小结:到了这里其实已经把LINQ的一些准备工作讲完了,从一系列的语法增强到.NET5.0的类库的添加,已经为后面的LINQ的到来铺好了道路。下面的几个小结将是最精彩的时刻,请不要错过哦。

原文出处:/content/3682943.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: