详解LINQ入门(中篇)

前 言

在上篇中简单的分享了LINQ的基础概念及基础语法,如果没有阅读过上篇的朋友可以点击这里。感谢大家的支持,本篇我们将更进一步的学习LINQ的一些相关特性及应用方法。废话不多说,请往下阅读吧。

延迟加载

在上篇中简单的和大家提到了LINQ具有一个很有意思的特性那就是“延迟加载”(或“延迟计算”),什么是延迟加载呢?先看来自官方的描述:延迟执行意味着表达式的计算延迟,直到真正需要它的实现值为止。是不是觉得有点生涩难理解呢?按照我个人的理解通俗的讲就是,每当我们编写好一段LINQ表达式时,此时这个表达式所代表的序列变量仅仅只是一个代理,编译器在执行编译时根本就不鸟这段代码,检查完语法正确性后直接跳过,直到代码在编译器动态运行序列变量在其他代码块被调用时,它所代理的linq表达式才会执行。啊~~看到这里你是不是要晕了,到底要怎么理解啊,无废话上代码:

// 已知一个序列
var array = new int[] {1, 2, 3};

// 编写一段LINQ表达式获得一个序列变量query
// 注意,这个变量仅仅是一个代理,在执行编译的时候,编译器检查完
// 该代码的正确性后直接跳过,不再理会
var query = from arr in array
      where arr > 1
      select arr;

// 调用上述序列变量query,此时上述的LINQ表达才会执行。注意此时已是在
// 编译器Runtime 的情况下执行
foreach(var q in query)
  Console.WriteLine(q.ToString());

如果你觉得上述例子不能让你有个深刻的理解,那么请看来自MSDN的例子

public static class LocalExtensions
{
  public static IEnumerable<string>
   ConvertCollectionToUpperCase(this IEnumerable<string> source)
  {
    foreach (string str in source)
    {
      Console.WriteLine("ToUpper: source {0}", str);
      yield return str.ToUpper();
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    string[] stringArray = { "abc", "def", "ghi" };
    // 这里方法 ConvertCollectionToUpperCase 是不会在编译时进行调用核查的,直到下面的foreach调用变量 q 此方法才会执行
    var q = from str in stringArray.ConvertCollectionToUpperCase()
        select str;

    foreach (string str in q)
      Console.WriteLine("Main: str {0}", str);
  }
  }

注意,ConvertCollectionToUpperCase 是一个静态扩展方法,后续讲解,如果你对.net 2.0 的 yeild 不熟悉的网上查阅吧,这里就不做介绍了。

// 输出结果

// ToUpper: source abc

// Main: str ABC

// ToUpper: source def

// Main: str DEF

// ToUpper: source ghi

// Main: str GHI

小结,延迟加载有好也有坏,由于是在Runtime的情况下执行序列,所以就容易造成未知异常,断点打错等等,所以编码LINQ是一定要考虑到它的这个特性。

lambda 表达式

了解完延迟加载后,那么现在我们需要简单的学习一下.net 3.5 给我们带来的新特性lambda表达式,在上篇的评论中,有园友问lambda和linq有什么关系,在这里其实他们没有任何关系,是完全不同的东西,但是我们为什么要掌握它呢?因为在后续的学习中会使用大量的lambda表达,他可以使我们的代码更优雅更有可读性,大大提高了我们的编码效率。

那么在学习lambda之前,先来回顾一下.net 2.0给我们带来的委托 delegate ,这个你一定不会感到陌生吧,而且一定会常用他。对于委托这里就不做详细的介绍了,要复习委托的在网上查阅吧。通过委托,我们可以得到一个东西“匿名方法”。咦,是不是觉得很眼熟,呵呵,用代码来加深回忆吧

public delegate void SomeDelegate1;
public delegate void SomeDelegate2(arg 1, arg 2);

// 匿名方法
SomeDelegate1 del1 += delegate() {...};
SomeDelegate2 del2 += delegate(arg1, arg2) {...}

上面的代码中我们看到在.net 2.0时代,我们可以通过delegate创建匿名方法,提高编码的灵活性,那么lambda和这个有什么关系呢,lambda对匿名方法进行了升华。看代码:

public delegate void SomeDelegate1;
public delegate void SomeDelegate2(arg 1, arg 2);

// 匿名方法
SomeDelegate1 del1 += () => {...};
SomeDelegate2 del2 += (arg1, arg2) => {...}

呵呵,是不是觉得有点不可思议呢,言归正传什么是lambda表达式呢,来自官方的定义:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。 该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。 Lambda 表达式 x => x * x 读作“x goes to x times x”。在定义里提到了表达式树,这是高阶晋级的话题,这里就不做讨论了,我们先把精力放在入门与实战应用上。

常规的lambda表达式如下:

(parameters) => { }

当指定的委托类型没有参数是表达式可以如下

() => { } 例:() => {/*执行某些方法*/}

如果表达右侧花括号里只有一个表达例如一元表达式,二元表达式等等,又或者是一个方法时那么花括号可以省略如下:

(x) => x; // 最简表达式

(x, y) => x == y;

() => SomeMethod();

注意,如果右侧的表达式存在花括号"{}",而且委托是具有返回类型的,那么表达式必须带上 return 关键字,如下:

(x, y) => {return x == y;};

到此我们已对lambad 表达式有了一定的掌握与了解。那么我们扩展一下,在.net 3.5中,ms 给我们提供了两个泛型委托 分别是 Fun<T> 和 Action <T> 他们可以帮助我们省去了返回创建常用委托的麻烦,提高编码效率。

共同点:它们至多提供委托传递6个参数(任意类型);

不同点:Fun 要求必须具有返回类型,而Action则必须不返回类型,规定返回 void

示例:

Fun<int, int, bool> fun = (a, b) => a==b;
Action<string> action = (p) => Console.Write(p);

小结,lambda 对我个人而言是个又爱又恨啊,不过爱多一点,它使我们写更少的代码做更多的事,但是在调试时一旦修改表达式内容,那么当前调试要么停止,要么重新开始,ms要是在这方面做得更完美些就好啦。不过它也间接提醒我们要有好的编码设计思维。

静态扩展方法

说完lambda,那么我们就进一步了解一下.net 3.5的另一个新特性“静态扩展方法”,什么是静态扩展方法呢,官方定义:扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。简单的说就是我们可以向一个已知的类型在不通过继承,复写等操作的情况下添加一个方法,以便类型的实例可以直接使用该方法。示例如下:

static class A

{

  // 这里的p1,p2 仅作为示例,实际中我们不一定需要

  public static int ExtendMethod1(this string input,string p1, string p2)

  {

     return int.Parse(input + p1 + p2);

  }

   // 泛型方法

   public static TOutput, ExtendMethod2<TOutput>(this obj);

   {

      return (TOutput)obj;

   }

}

class B
{
  void Main()
  {
   var a = "1";
   var result = a.ExtendMethod1("2","3");
   // result:123
  }
}

注意,方法的 static 是必须的,而且需在静态类里。第一个参数 this 是必须的,紧跟着 this 后面的需要扩展的类型实例参数,也是必须的。至于后面的方法调用传递参数就因个人所需了。

既然我们学习了静态扩展方法,那么它和LINQ又有什么关系呢?在System.Linq的命名空间中提供了大量的静态扩展方法,这些静态方法本身就对linq表达式的封装,这样我们就可以省去了编写简单的linq表达式的步骤。如下:

var array = new int[]{1,2,3,4,5};

var query1 = from arr in array

       select arr;

var query2 = array.Select(e => e);

上面的示例中 query1 和 query2 是等价的,通过 query2 我们是不是又可以偷懒了很多,呵呵。

再来点带where的

var array = new int[]{1,2,3,4,5};

var query1 = from arr in array

       where arr > 2

       select arr;

var query2 = array.Where(e => e > 2);

再来一个复合型的

var array = new int[]{1,2,3,4,5};

var max = (from arr in array.Where(e => e > 2)

      select arr).Max();

是不是觉得很cool。由于篇幅的关系在这里就不逐一的去接受这些静态方法了,下面是一些常用的静态方法列表,感兴趣的去MSDN查阅详细吧。

Aggregate , All , Any , AsEnumerable , Average , Cast , Concat , Contains, Count, DefaultIfEmpty , Distinct , ElementAt, ElementAtOrDefault ,Empty , Except , First, FirstOrDefault , GroupBy , GroupJoin , Intersect , Join , Last , LastOrDefault , LongCount , Max , Min , OfType ,OrderBy ,OrderByDescending , Range , Repeat , Reverse , Select , SelectMany , SequenceEqual , Single , SingleOrDefault , Skip , SkipWhile , Sum ,Take, TakeWhile , ThenBy , ThenByDescending , ToArray , ToDictionary , ToList, ToLookup, Union,Where

Cast<T> 和 OfType<T> 静态扩展方法

在最后我们还是要注意两个常用的静态方法Cast<T>, OfType<T> 。它们的共同点是都能把非IEnumerable<T> 类型的集合转换成IEnumerable<T>类型,然后再

进行LINQ操作,如下

var dt = new DataTable();
dt.Columsn.Add("A", typeof(int));

var newRow1 = dt.NewRow();
newRow1["A"] = 1;

var newRow2 = dt.NewRow();
newRow2["A"] = 2;

dt.Rows.Add(newRow1);
dt.Rows.Add(newRow2);

var query1 = dt.Rows.Cast<DataRow>().Select(e=>(int)e["A"]);
var query2 = dt.Rows.OfType<DataRow>().Select(e=>(int)e["A"]);

这样我们就可以得到看上去两个相同的序列,在这里要注意:MSDN上的说明存在误导,MSDN对于OfType<T>的解释存在偏差,实际上经本人反复敲代码验证,得到的结论是,Cast对序列进行强制转换,一旦转换不成功则抛出异常。OfType则是一旦转换不成功,则不会抛出异常,但是将会得到一个空序列。见下面代码:

var arr1 = new string[] { "1","2","test" };
var arr2 = arr1.Cast<int>();
var arr3 = arr1.OfType<int>();

//通过Cast转换,则会抛出异常
foreach (var i in arr2)
   Console.WriteLine(i.ToString());

//通过OfType转换,有异常但是不会抛出并得到一个空序列
foreach (var i in arr3)
   Console.WriteLine(i.ToString());

Console.Read();

总 结

本文到此,我们已对LINQ涉及的应用有了进一步的了解。学习什么是linq的延迟加载,lambda和linq是否有暧昧关系。以及静态扩展方法对linq的辅助作用。也许你会问既然可以用静态扩展方法替代编写linq,那么二者怎么择取呢,据砖家叫兽提议我们应该先以linq命名空间下的静态扩展方法为主,实在是很复杂的linq表达式,我们再考虑使用linq本身的表达式编写。后续我们将分享学习LINQ更贴近实战应用的知识,linq to dataset, linq to , linq to sql, linq to entities.

感谢您的阅读,如果有说得不对的地方请指正。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解LINQ入门(下篇)

    前 言 终于来到下篇了,通过上篇,和中篇,我们了解了linq的基本语句,对应linq我们又了解到lambda表达式,静态扩展方法,以及linq的延迟加载的特性,那么在本篇文章中我们将分享学习一下linq对于我们开发中常用到的对象的操作应用.如果没有阅读过上篇的请点击这里,如果没有阅读中篇的请点击这里 linq to DataSet 对于做.net 开发的有谁不知道DataSet,DataTable,DataRow,DataColumn这些对象,如果你真的不知道,那好吧建议你到菜市场买2块豆腐撞死

  • 详解LINQ入门(上篇)

    前 言 最近和我们老大一起做技术面试(我是旁听的),发现前来面试的没几个掌握甚至是丁点了解LINQ.这让我很纳闷,LINQ伴随2008一起发布至今难道大家真的没时间去了解一下或者学习一下这个应用基础吗.甚至问及有些人LINQ是什么,答题者想都不想 LINQ TO SQL, 崩溃!没错,LINQ是可以TO SQL,但是除了SQL,LINQ就无所作为了?非也.因此在这里和大家一起分享学习LINQ.本文适合以下读者, 如果你是不符合者请赏脸捧个场,3Q 从未触碰过LINQ的 对LINQ有过了解但是从未

  • 详解LINQ入门(中篇)

    前 言 在上篇中简单的分享了LINQ的基础概念及基础语法,如果没有阅读过上篇的朋友可以点击这里.感谢大家的支持,本篇我们将更进一步的学习LINQ的一些相关特性及应用方法.废话不多说,请往下阅读吧. 延迟加载 在上篇中简单的和大家提到了LINQ具有一个很有意思的特性那就是"延迟加载"(或"延迟计算"),什么是延迟加载呢?先看来自官方的描述:延迟执行意味着表达式的计算延迟,直到真正需要它的实现值为止.是不是觉得有点生涩难理解呢?按照我个人的理解通俗的讲就是,每当我们编写

  • 详解webpack 入门与解析

    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档. webpack是基于node的.先安装最新的node. 1.初始化 安装node后,新建一个目录,比如html5.cmd中切到当前文件夹. npm init -y 这个命令会创建一个默认的package.json.它包含了项目的一些配置参数,通过它可以进行初始安装.详细参数:https://docs.npmjs.com/files/package.json. 不要y参数的话,会在命令框中设置各项参数,但

  • 详解Unity入门之GameObject

    GameObject和Component GameObject是游戏场景中真实存在的,而且有位置的一个物件 Component附属于GameObject,控制GameObject的各种属性 GameObject是由Component组合成的,Component的生命周期和GameObject息息相关.调用此GameObject的Destroy方法,它的子对象和对应的所有Component都会被销毁,但也可以一次只销毁一个Component 常见的Component: Component 作用 R

  • 实例详解SpringMVC入门使用

    MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller).通过分层使开发的软件结构更清晰,从而达到开发效率的提高,可维护性和扩展性得到提高.Spring提供的MVC框架是在J2EE Web开发中对MVC模式的一个实现,本文通过实例讲解一下Spring MVC 的使用. 先来看一个HTTP request在Spring的MVC框架是怎么被处理的:(图片来源于Spring

  • 详解Puppeteer 入门教程

    1.Puppeteer 简介 Puppeteer 是一个node库,他提供了一组用来操纵Chrome的API, 通俗来说就是一个 headless chrome浏览器 (当然你也可以配置成有UI的,默认是没有的).既然是浏览器,那么我们手工可以在浏览器上做的事情 Puppeteer 都能胜任, 另外,Puppeteer 翻译成中文是"木偶"意思,所以听名字就知道,操纵起来很方便,你可以很方便的操纵她去实现: 1) 生成网页截图或者 PDF 2) 高级爬虫,可以爬取大量异步渲染内容的网页

  • 详解React-Todos入门例子

    最近学完React的最基本概念,闲下来的时候就自己写了一个Todo-List的小应用.这里做个简略的说明,给想好好学React的新手看. 开始之前 这里我用了webpackb做了babel和JSX预处理和模块打包.所以对React和一些ES2015(ES6)的语法要有一定的了解.我相信学习ES2015绝对是划算的,因为它是Js的规范.这里给出学习的地方,阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015. 最后的实际效果: 我们需要做到的功能有: 可以在最

  • 详解webpack 入门总结和实践(按需异步加载,css单独打包,生成多个入口文件)

    最近在项目中使用了一下webpack,所以这里打算对目前了解的使用方法做一个小小的总结 为什么是webpack 1.webpack一下自己就

  • 详解ECMAScript6入门--Class对象

    面向对象的语言有一个标志,那就是他们都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象. ECMAScript5中没有类的概念,因此它的对象和基于类的语言中的对象有所不同. Javascript生成对象的传统方法是通过构造函数来实现的 function Person(name, age){ this.name = name; this.age = age; this.sayHello = function(){ return "Hello "+ this.name; } }

随机推荐