这篇文章其实几天前就打算写了,但由于这几天一直忙于各种事务和工作,所以一直无暇提笔,十分抱歉,和上一篇一样,在进入今天的主题之前先贴上这篇文章一些来自msdn的参考资料。
http://msdn.microsoft.com/zh-cn/library/bb397924.aspx
http://msdn.microsoft.com/zh-cn/library/bb397896.aspx
http://msdn.microsoft.com/zh-cn/library/system.linq.enumerable.aspx
我们先来看一个我们经常写的查询操作。
var query = from item in dataContext.Customers
where item.City == aa //&& item.CustomerId == 1
select item;
foreach(var item in query)
{
//do something
}
接下来我们就一步一步来分析这个例子中的类型推断所要用的各个知识点和其中的联系。
首先,让我们来分析下var关键字。
对于var关键字,首先我们要清楚.net中var关键字声明的变量也是强类型。
然后,var关键字声明的对象必须是在声明时就必须能推断出其类型的,所以,声明一个var类型对象时候就就必须对它进行初始化,让编译器能推断出它的类型,所以,这样的代码是无法编译过的
var query2;
query2 = from item in dataContext.Customers
where item.City == aa //&& item.CustomerId == 1
select item;
综上所述,所有的linq查询语句的结果(query)都必然是某种强类型,那么,它究竟是什么类型呢?
请读者回顾上我们上一节中所提到的内容,想想查询的过程:查询无非由四种关系组成——投影,筛选,聚合,排序。上一节中我们提到过,延迟执行的linq可查询的数据源都必须是实现了IEnumerable<T>接口的,而大家想想:第一,查询是不是可以理解成从数据源中提取出某些数据,而这些数据的的存放结构是不是并没改变,第二,一次查询的结果的是不是可以作为下一次查询的数据源。所以,每次查询后的结果query依然是实现了IEnumerable<T>接口的,即每次查询的结果query都是IEnumerable<T>这个类型,所以上面的查询可以写为
IEnumerable<Customer> query2 = from item in dataContext.Customers
where item.City == aa //&& item.CustomerId == 1
select item;
下面贴几个msdn上的关于类型推断的图:
a.下图演示不对数据执行转换的 LINQ to Objects 查询操作。
1. 数据源的类型参数决定范围变量的类型。
2. 选择的对象的类型决定查询变量的类型。此处的 name 为一个字符串。因此,查询变量是一个IEnumerable<string>。
3. 在 foreach 语句中循环访问查询变量。因为查询变量是一个字符串序列,所以迭代变量也是一个字符串。
b.转换源数据的查询
1. 数据源的类型参数决定范围变量的类型。
2. select 语句返回 Name 属性,而非完整的 Customer 对象。因为 Name 是一个字符串,所以 custNameQuery 的类型参数是 string,而非 Customer。
3. 因为 custNameQuery 是一个字符串序列,所以 foreach 循环的迭代变量也必须是 string。
下面将来说说我今天所写的另一个核心内容,我们先来想一个问题:是不是所有的linq查询都是用的一套东西,或者说,是不是所有的linq查询都只是返回IEnumerable<T>?
答案是否定的。
细心的读者可能已经发现了,在我上面所贴的2个msdn的例子中已经显示写出了linq查询所返回的2套东西——IEnumerable<T>和IQueryable<T>。
类继承关系:public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable。
引用msdn上的介绍:
共有两组 LINQ 标准查询运算符,一组在类型为 IEnumerable<T> 的对象上运行,另一组在类型为 IQueryable<T> 的对象上运行。构成每组运算符的方法分别是 Enumerable 和 Queryable 类的静态成员。这些方法被定义为作为方法运行目标的类型的“扩展方法”。这意味着可以使用静态方法语法或实例方法语法来调用它们。
大家应该还记得,上节我们说过linq查询要执行在clr上师把查询语句变成扩展方法来执行,这两套东西不仅返回类型不同连所定义的扩展方法都不同,所以我们完全可以把这2套东西理解成完全不同的东西,那么,什么时候用IEnumerable<T>,什么时候用IQueryable<T>呢?让我们再来看看msdn的介绍:
对于在内存中集合上运行的方法(即扩展 IEnumerable<T> 的那些方法),返回的可枚举对象将捕获传递到方法的参数。在枚举该对象时,将使用查询运算符的逻辑,并返回查询结果。
与之相反,扩展 IQueryable <T> 的方法不会实现任何查询行为,但会生成一个表示要执行的查询的表达式树。查询处理由源 IQueryable<T> 对象处理。
一言以蔽之,本地数据源用IEnumerable<T>,并且查询的逻辑可以直接用你所定义的方法的逻辑(因为有上下文),远程数据源用IQueryable<T>,无法直接使用你所定义的方法的逻辑,必须先生成表达式树,查询由源对象处理。
下面我们再来看一个例子来证明这是两套完全不同的东西:
首先是本地数据源:
List<string> names = new List<string> { "Cai", "Wxied", "Beauty" };
然后我们看看names的where方法
VS的智能提示会告诉我们(sorry,这个地方实在不好截图,大家可以自己尝试,我先给大家描述下)这个扩展方法有2个重载,必须传入Func<T>,返回IEnumerable<T>。
再提一点知识,Func<T>叫谓语表达式,相当于一个委托,我认为,之所以可以直接传Func<T>是因为本地数据源可以直接执行方法的逻辑。
再让我们来看看一个远程数据源:
DataClasses1DataContext dataContext = new DataClasses1DataContext();
dataContext.Customers.Where这个方法有4个重载。必须传入Expression<Func<T>>,返回IQueryable<T>。
大家和上面对比一下,就会发现本地数据源和远程数据源的扩展方法完全不一样,而且远程数据源不能直接传Func<T>,必须用一个Expression来包装这个Func<T>,正好又从一个方面验证了我们之前所提到的知识。
ok,今天就讲到这里,非常希望大家能跟我交流,十分遗憾上一篇文章发表到现在一篇评论都没有。。。。然后,下一节我们将从IQueryable<T>的这套东西深入下去,开始研究表达式和表达式树,谢谢您的耐心阅读。