在我们去深入了解linq之前,有几个重要的基础需要先了解复习一下。
var是C#3.0时新增的特性,现在这个var会大量的出现在我的代码中,首先,我先从实际的感受表达一下,这个关键字给我带来的实实在在的好处:
在这里,我不打算讲var的一些普通的特性,比如var的通用用法,比如减少程序员的工作量等,我打算重点讲的是针对于Linq部分var的强大,但是对于var 用法的一些注意点,还是要提一下,如下:
好了,我们到了var使用的重点了,匿名类型:
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。类型名由编译器生成,并且不能在源代码级使用。每个属性的类型由编译器推断。
我们先看下面一个例子:
var o = new { Name = "Jack", Address = "SH", Age = 26 };
此时,编译器会替我们生成一个匿名类型:
具体编译器生成的类型,我们通过Reflector工具可以查看如下:
我们再看一个例子:
var o1 = new { Name = "Jack", Address = "SH", Age = 26.0 };
此时编译器不会新增新的类型定义,编译器会延用之前生成好的泛型的匿名类型,只不过现在TAge编译器推断出为double类型
我再看一个:
var o2 = new { Name = "Jack", Age = 26, Address = "SH" };
与o1的定义来看,貌似只是字段的顺序发生了变化,对于这种情况,编译器会新增匿名类型吗?出乎我的意料,编译器会新增一个匿名类型,如下:
这是为什么?我最开始认为他们无非是顺序发生了变化,应该可以继续使用之前存在的匿名类型呀?仔细观晓,得出的理由大概如下:我们看下第一个匿名类型的构造方法:
再看看第二个:
构造方法中的参数是有顺序的。所以,这个问题就明白了。我们再来看一个例子:
var o3 = new { Name = "Jack", Address1 = "SH", Age = 26 };
此时,Address的属性名称发生了变化,变成了Address1,毫无疑问,编译器会新增一个新的匿名类型,如下:
结论:
如果程序集中的两个或多个匿名对象初始值指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。它们共享同一编译器生成的类型信息。
匿名类型在Linq中的应用,终于到了这节的重点了。
我们以Enumerable扩展方法中Select方法为例,其实匿名类型通常就是用在查询表达式select子句中。
public static IEnumerable Select(this IEnumerable source, Func selector);
这个方法的意思是:将序列中的每个元素投影到新表中。
来个例子:
List list = new List()
{
new Persion(){Name="Jack",Address="SH",Age=26},
new Persion(){Name="Lee",Address="GZ",Age=27},
new Persion(){Name="Lucy",Address="JJ",Age=28}
};
var o = list.Select(r => new { NewName = r.Name, NewAddress = r.Address });
此时TSource参数是Persion类型,这点是可以推断的。
此时TResult参数是什么类型?显然这是一个匿名类型,这个匿名类型是编译器生成的,它有2个只读字段,NewName和NewAddress。
所以上面的代码我们假设可以这样写:
IEnumerable<匿名类型> o = list.Select(r => new { NewName = r.Name, NewAddress = r.Address });
但是,你要知道,这个匿名类型具体的类名,或者叫符号,只有编译器知道,程序员是不知道,所以,你只能用var 代替 IEnumerable<匿名类型> ,依靠编译器去推断,去识别。
怎么样,现在是不是觉得豁然开朗了。然而,这只是接触Linq的第一步要了解的内容。
扩展方法,站在语言角度上去讲,这算不上一个新鲜东西,因为我们都知道,这其实是微软提供的一个语法糖而已,本质是其实还是利用静态方法对类型的一些功能进行扩展而已,这一点后面会讲到。
我们先假设.NET中没有提供扩展方法,现在有这么一个情景,我们要对一个类型进行功能扩展,我们会通过什么方法呢?
举个例子,我们要对SomeType进行扩展:
public static class SomeTypeExtension
{
public static SomeType ExtensionMethod(SomeType s)
{
//to do something for s;
return s;
}
}
SomeType s = new SomeType();
//当我们要连续对s调用扩展方法时,下面的方法就显的有丑陋,而且可读性非常差
SomeTypeExtension.ExtensionMethod(SomeTypeExtension.ExtensionMethod(SomeTypeExtension.ExtensionMethod(s)));
//我们期望的能像JQuery使用风格一样,比如我们期望的是这样:
s.ExtensionMethod().ExtensionMethod().ExtensionMethod();
微软当然想到了,于是,扩展方法就提出来了。(注意:我们在讨论的前提是,用静态方法扩展原有类情况下不友操作)
我们看下用扩展方法来实现:
public static class SomeTypeExtendsion
{
public static SomeType ExtendsionMethod(this SomeType input)
{
// to do something for input;
return input;
}
}
SomeType s = new SomeType();
s.ExtendsionMethod().ExtendsionMethod().ExtendsionMethod();
扩展方法其本质就是开始我们的实现方法。我们可以通过Reflector中得知,如下:
关于扩展方法,大家都很熟悉了,之所以要讨论这个,主要是Linq的实现全部是基于扩展方法来实现的。这将会是我后面文单要分析的重点。
扩展方法我不打算多讲,其主要要注意几点:
类型推断发生的情况有很多种,例如,隐式类型的数组是会进行类型推断,方法作为一个实参传递给一个形参为委托方法时,会将方法进行类型推断,推断成一个委托,而现在我的重点不在这些情况,而在另一种普遍的情况,即使用一个泛型方法,而没有为方法指定类型实参。这种情况下的类型推断,是Linq实现的一个重要基础。
public static T2 SomeMethod(T1 t, Func fun)
{
return fun(t);
}
int i = SomeMethod("helloworld!", r => r.Length);
此时编译器会按照参数顺序根据传递的实参来进行推断:
1、第一个实参是“helloworld!”编译器假设T1为string类型
2、第二个实参是Fun
假如,编译器不提供类型推断,则需要我们在调用方法时显式的写出类型参数,如下:
int i = SomeMethod("helloworld!", r => r.Length);
这样好样没什么大不了,我们无非是要我们指定类型实参而已,但是你看下面Linq的这种情况:
public static IEnumerable Select(this IEnumerable source, Func selector);
List list = new List()
{
new Persion(){Name="Jack",Address="SH",Age=26},
new Persion(){Name="Lee",Address="GZ",Age=27},
new Persion(){Name="Lucy",Address="JJ",Age=28}
};
var o = list.Select(r => new { NewName = r.Name, NewAddress = r.Address });
如果编译器不提供类型推断时就必须如下写法:
var o = list.Select(r => new { NewName = r.Name, NewAddress = r.Address });
而匿名类型只有编译器才知道,程序员是无法知道匿名类型具体是什么类型的。
现在我们明白类型推断对于Linq的重要性了。
以上四节都是为我们去研究Linq toObject 、Linq to SQL 、Linq to XML一些必须要了解的基础,linq的也是基于以上4点来实现的。
当然还有一些信息,也是linq实现的基础,例如迭代器的实现,yield return 这里也是与linq实现相关,在这里我就先不讲了。