在《C# in Depth》的第11章,作者花了大量篇幅深入介绍了LINQ to Objects的相关知识,包括LINQ的基本概念、查询语句中的类型转换、筛选与排序、Join、Grouping等。但是坦白说,我认为本章大部分内容仍然属于“语法指导”的范畴,这些都可以通过阅读微软官方文档获取,所以还是略有点失望的。本章对于我来说最值得写下来的是对“流与延迟执行”(streaming and deferred execution)的概念的介绍,因为这个是我知识点的空白。
流与延迟执行
直接用书中的例子来说明,加入有如下查询语句:
var
我们要从一个Person类型的集合里取出所有Age属性大于18的成员的Name属性的集合。这段语句执行时,并不会从数据源加载整个集合,而是以流的形式逐个成员加载,下图是作者画出的这段语句的时序图。
执行代码时,程序会每次取出一个成员来执行查询,如果这个成员符合Age≥18的条件,则将它的Name属性“select出来”,存入“结果集合”中,然后再取出下一个成员,依次执行。这就是延迟执行(deferred execution)的概念。
延迟执行可以有效的减少内存消耗以及提高性能,避免将集合一次性整个加载到内存中。但是,并不是所有的LINQ语句都具有延迟执行的特性,例如Enumerable.Reverse方法要求将集合元素的排序反转,就要求必须将整个集合加载到内存中再执行后面的操作,它就无法延迟执行,只能“缓存操作”(buffering operation)。
流和延迟操作的概念,对于程序设计也是很有参考意义的,有条件的情况下,我们应该尽量设计“流”式操作,而不是“缓存”式操作。
LINQ查询与普通方法的选择
我们知道,LINQ查询表达式实际上类似一种语法糖,它在编译时会被翻译成普通C#语句。例如,上面的例子中的LINQ语句会翻译成以Lambda表达式为参数的扩展方法形式:
// LINQ查询表达式
因此,在实际使用中完全可以用Lambda表达式替代LINQ,一个C#程序组即使完全不用到LINQ也是很正常的事情。对于我来说,我认为在大多数情况下Lambda表达式的写法更加优雅达意,所以我也没怎么用过LINQ。
那么什么时候LINQ比Lambda更好用呢?作者认为,在需要用到匿名类型作为查询的中间载体时,以及涉及到多表达式联合查询(join)时,LINQ具有更简单的写法和更好的可读性。书中有例子,我就不再搬运了。