您的位置:首页 > 移动开发 > Objective-C

Linq to Object 深入理解(一):了解Linq之前的基础知识

2015-06-03 22:06 513 查看
在我们去深入了解linq之前,有几个重要的基础需要先了解复习一下。

一、隐式类型var

var是C#3.0时新增的特性,现在这个var会大量的出现在我的代码中,首先,我先从实际的感受表达一下,这个关键字给我带来的实实在在的好处:

对于我这个懒惰的人来讲,这个关键字解救了我,假设你遇到这一行代码:ClassOuter.ClassInner1.ClassInner2.ClassInner3obj= CreateClassInner3(Param1,Param2,Param3,Param4);你在敲左边代码的时候是不是感觉有点痛苦,你必须要完整的打出变量的类型,可能这个变量的类型是很长很长,或者更尴尬的是,你可能记不清楚这个类型,相信你也会有这样的感觉,现在有了关键字,var obj=CreateClassInner3(param1,param2,param3,param4);就可以解决了,好爽,我又少敲了些代码,而且最重要的是,对于我这种赋值语句必须要在一行完成的强迫症的人来说,这太好了,即偷懒了,又美观,所以都交给编译器处理吧,话说微软貌似感觉想把程序员变的越来越依懒编译器了。
 如果你觉得var只是微软想减轻程序员的工作,那就想的太简单了,现在看来,微软在提出var的时候是有野心,有目的,我想你已经猜出来了,对的,Linq(尽管当时Linq是在以C#3.5中提出的),在linq中var会发挥他强大的优势。

在这里,我不打算讲var的一些普通的特性,比如var的通用用法,比如减少程序员的工作量等,我打算重点讲的是针对于Linq部分var的强大,但是对于var 用法的一些注意点,还是要提一下,如下:

 var只能出现在方法内部的变量声明,而不能作为类中字段类型的声明
使用var时,右边的表
4000
达工或者变量必须有明确的类型,例如,你不能这样使用var o=null; 此时 null 的含义不明,他可以看作是任务引用类型,因此编译器无法判断o具体的类型;
使用var时不能只声明,后定义,例如:  var o;
 不要滥用var,并不是什么地方使用var都能带来好处的,站在开发过程中看来,貌似var 比大部分类型要精简很多,但是你想一下,你在写代码的时候,是很清楚你右边表达式的内容,但是另一个程序员在看你代码的时候,就会很痛苦了,因为他无法第一眼看出变量的实际类型,因此在实际的开过程中,除非很有必要(例如linq中,就不得不用var),开发准则中还是要求要写出完整的类型名。当然你自己写测试代码时就无所谓啦!

二、匿名类型

好了,我们到了var使用的重点了,匿名类型:
匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。类型名由编译器生成,并且不能在源代码级使用。每个属性的类型由编译器推断。
        我们先看下面一个例子:   

var o = new { Name = "Jack", Address = "SH", Age = 26 };


此时,编译器会替我们生成一个匿名类型:

这个匿名类型是基于泛型的,一共有三个泛型参数TName(代表Name的类型),TAddress(代表Address的类型),Tage(代表Age的类型)。
这个匿名类型有三个属性,Name,Address,Age。
这个匿名类型中的泛型参数编译器推断出TName为string,TAddress为string,TAge为int。
Name、Address、Age这几个属性是ReadOnly的。
这个匿名类型中没有无参构造方法。
这个匿名类型中重写了ToString、GetHashCode、Equal方法。

具体编译器生成的类型,我们通过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<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);


这个方法的意思是:将序列中的每个元素投影到新表中。
来个例子:

List<Persion> list = new List<Persion>()
{
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中没有提供扩展方法,现在有这么一个情景,我们要对一个类型进行功能扩展,我们会通过什么方法呢?

继承基类,在子类中新增方法,此方法是我们最容易想到的方法,但是这存在一定的限制,比如基类是sealed的呢
新增一个工具类,比如一个静态类,提供一些专门的方法,对原有类进行功能扩展,这是一种通用的方法,基本没有什么限制。但有一种情况比如linq中链式风格操作,这种方法会显的丑陋不堪(在使用Linq操作风格和JQuery中的内格之后你会这样感觉)

举个例子,我们要对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的实现全部是基于扩展方法来实现的。这将会是我后面文单要分析的重点。
扩展方法我不打算多讲,其主要要注意几点:

扩展类和扩展方法必须static类型
只允许扩展方法,不允许扩展属性、事件
注意扩展污染
扩展方法有可能重名,带来的岐议,和扩展方法的寻找路径
注意,扩展方法第一个参数中的this

四、类型推断

类型推断发生的情况有很多种,例如,隐式类型的数组是会进行类型推断,方法作为一个实参传递给一个形参为委托方法时,会将方法进行类型推断,推断成一个委托,而现在我的重点不在这些情况,而在另一种普遍的情况,即使用一个泛型方法,而没有为方法指定类型实参。这种情况下的类型推断,是Linq实现的一个重要基础。

public static T2 SomeMethod<T1, T2>(T1 t, Func<T1, T2> fun)
{
return fun(t);
}
int i = SomeMethod("helloworld!", r => r.Length);


此时编译器会按照参数顺序根据传递的实参来进行推断:
1、第一个实参是“helloworld!”编译器假设T1为string类型
2、第二个实参是Fun<string,T2>委托,根据委托的返回值r.length,推断出T2为int类型
这种情况是一个非常简单的例子,复杂的例子还会考虑重载决策的情况。

假如,编译器不提供类型推断,则需要我们在调用方法时显式的写出类型参数,如下:

int i = SomeMethod<string, int>("helloworld!", r => r.Length);


这样好样没什么大不了,我们无非是要我们指定类型实参而已,但是你看下面Linq的这种情况:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

List<Persion> list = new List<Persion>()
{
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<Persion,匿名类型>(r => new { NewName = r.Name, NewAddress = r.Address });


而匿名类型只有编译器才知道,程序员是无法知道匿名类型具体是什么类型的。
现在我们明白类型推断对于Linq的重要性了。

五、总结

以上四节都是为我们去研究Linq toObject 、Linq to SQL 、Linq to XML一些必须要了解的基础,linq的也是基于以上4点来实现的。
当然还有一些信息,也是linq实现的基础,例如迭代器的实现,yield return 这里也是与linq实现相关,在这里我就先不讲了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linq