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

在我们去深入了解linq之前,有几个重要的基础需要先了解复习一下。

一、隐式类型var

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

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

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

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

二、匿名类型

好了,我们到了var使用的重点了,匿名类型:

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

        我们先看下面一个例子:   

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

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

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

具体编译器生成的类型,我们通过Reflector工具可以查看如下:

Linq to Object 深入理解(一):了解Linq之前的基础知识_第1张图片

我们再看一个例子:

var o1 = new { Name = "Jack", Address = "SH", Age = 26.0 };

此时编译器不会新增新的类型定义,编译器会延用之前生成好的泛型的匿名类型,只不过现在TAge编译器推断出为double类型

我再看一个:

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

与o1的定义来看,貌似只是字段的顺序发生了变化,对于这种情况,编译器会新增匿名类型吗?出乎我的意料,编译器会新增一个匿名类型,如下:

Linq to Object 深入理解(一):了解Linq之前的基础知识_第2张图片

这是为什么?我最开始认为他们无非是顺序发生了变化,应该可以继续使用之前存在的匿名类型呀?仔细观晓,得出的理由大概如下:我们看下第一个匿名类型的构造方法:

Linq to Object 深入理解(一):了解Linq之前的基础知识_第3张图片

再看看第二个:

Linq to Object 深入理解(一):了解Linq之前的基础知识_第4张图片

构造方法中的参数是有顺序的。所以,这个问题就明白了。我们再来看一个例子:

var o3 = new { Name = "Jack", Address1 = "SH", Age = 26 };

此时,Address的属性名称发生了变化,变成了Address1,毫无疑问,编译器会新增一个新的匿名类型,如下:

Linq to Object 深入理解(一):了解Linq之前的基础知识_第5张图片

结论:

如果程序集中的两个或多个匿名对象初始值指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。它们共享同一编译器生成的类型信息。

匿名类型在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 to Object 深入理解(一):了解Linq之前的基础知识_第6张图片


怎么样,现在是不是觉得豁然开朗了。然而,这只是接触Linq的第一步要了解的内容。

三、扩展方法

扩展方法,站在语言角度上去讲,这算不上一个新鲜东西,因为我们都知道,这其实是微软提供的一个语法糖而已,本质是其实还是利用静态方法对类型的一些功能进行扩展而已,这一点后面会讲到。

我们先假设.NET中没有提供扩展方法,现在有这么一个情景,我们要对一个类型进行功能扩展,我们会通过什么方法呢?

  1. 继承基类,在子类中新增方法,此方法是我们最容易想到的方法,但是这存在一定的限制,比如基类是sealed的呢
  2. 新增一个工具类,比如一个静态类,提供一些专门的方法,对原有类进行功能扩展,这是一种通用的方法,基本没有什么限制。但有一种情况比如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 to Object 深入理解(一):了解Linq之前的基础知识_第7张图片

关于扩展方法,大家都很熟悉了,之所以要讨论这个,主要是Linq的实现全部是基于扩展方法来实现的。这将会是我后面文单要分析的重点。

扩展方法我不打算多讲,其主要要注意几点:

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

四、类型推断

类型推断发生的情况有很多种,例如,隐式类型的数组是会进行类型推断,方法作为一个实参传递给一个形参为委托方法时,会将方法进行类型推断,推断成一个委托,而现在我的重点不在这些情况,而在另一种普遍的情况,即使用一个泛型方法,而没有为方法指定类型实参。这种情况下的类型推断,是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委托,根据委托的返回值r.length,推断出T2为int类型

这种情况是一个非常简单的例子,复杂的例子还会考虑重载决策的情况。

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

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实现相关,在这里我就先不讲了。


你可能感兴趣的:(Linq,系列,linq)