详解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.
感谢您的阅读,如果有说得不对的地方请指正。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。