CSharpThinking---查询表达式与Linq to Objects(五)
本章单独介绍查询表达式与Linq to Objects 的世界,展示其强大的语言特性与设计者的“编译器支持”理念。
本文不会按照“由浅入深”的思路来讲解概念,相反从特性的本质及关联讲起,最后简单介绍一些“小技巧”和需要注意的地方,着实有头重脚轻的感觉。--- 史蒂芬King
一,概念
1.1 查询表达式起源
C#语言的设计者决定在C#3中提供一个新的语法:查询表达式。
以此弥补复杂语句中标准查询运算符的难以理解、复杂与提高与Sql语句的相似度来便于学习(查询表达式与SQL最大的区别是from与Select的位置倒置,这是为IDE提供智能感知的”推断“类型功能)。
1 string[] Keywords = { "abstract", "Int", "base"}; 2 3 // “标准”查询表达式例子 4 var query = from word in Keywords 5 where !word.Contains("a") 6 select word; // 自处select最后会被编译器省略,稍后会讲到。
上面的例子是简洁版查询表达式的一个Demo,查询表达式其实是对底层API的一系列方法的调用。CIL本身并不理解“查询表达式”的概念。事实上,除了涉及表达式树的地方能使用查询表达式,但底层CLR根本没有改动,相反,查询表达式是通过
C#编译器的改动来支持这一特性的。
以上代码编译之后通过,IL查看的部分编码如下:
1 .field private static class [mscorlib]System.Func`2<string,bool> 'CS$<>9__CachedAnonymousMethodDelegate1' 2 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
1 .method private hidebysig static bool '<MyDemo>b__0'(string word) cil managed 2 { 3 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 4 // Code size 19 (0x13) 5 .maxstack 2 6 .locals init ([0] bool CS$1$0000) 7 IL_0000: ldarg.0 8 IL_0001: ldstr "a" 9 IL_0006: callvirt instance bool [mscorlib]System.String::Contains(string) 10 IL_000b: ldc.i4.0 11 IL_000c: ceq 12 IL_000e: stloc.0 13 IL_000f: br.s IL_0011 14 IL_0011: ldloc.0 15 IL_0012: ret 16 } // end of method '查询表达式'::'<MyDemo>b__0'
通过对编译后的DLL分析,得出两个结论:
1) 查询表达式确实被编译成了委托,也就是说是编译器执行了代码,而非运行时或CLR支持。
2) 由于编译时已经生成了委托,所以大多数查询表达式也是”延迟执行“方式。
注:
与延迟执行相反的是"即时执行":非返回IEnumerable或IQueryable类型的单一返回类型,但如果在查询表达式中整体上依然是延迟执行。例如: XXX.count()
1.2 查询表达式的流处操作与缓冲操作
1) 缓冲操作:将所有数据一次性加载进内存,然后运算,最后输出结果。如Enumerable.Revense。
2) 流操作:一次处理每个序列的一个元素,提高效率。如下:
1 // 伪代码 2 var query = from file in Directory.GetFiles(@"C:\CSharpInDepthLogs", "*.log") 3 from line in ReadLines(file) 4 let entry = new LogEntry(line) 5 where entry.Type == EntryType.Error 6 select entry; 7 8 Console.WriteLine("Total errors: {0}", query.Count());
3)join子句的内联:左边的子句是流处理方式,右边的子句是缓冲方式,这种操作依然是延迟操作。(原则:效率至上,所以尽可能的右边序列要小。)
1 // 语法 2 join right-range-variable in right-sequence 3 on left-key-selector equals right-key-selector
1.3 查询表达式与点标记(Dot notation)之间做出的选择
点标记:用普通的C#调用Linq查询操作符来代替查询表达式。(实际上编译器转化查询表达式也是如此的过程)
原则:更加的可读性及习惯性。查询表达式更像是函数式编程,结构化思维;而点标记适合查询条件比较少时清晰易懂。
查询表达式是”说明性“的,与之相对的是”命令性“的语言,说明性的语言关心的是操作步骤而绝非仅仅是结果。
1 // 查询表达式(复杂些) 2 var adults = from person in people 3 where person .Age > 18 4 select new { Name = person .Name, Age = person .Age} into result 5 orderby result.Age descending 6 select result; 7 8 // 点标记(简单些) 9 var adults2 = people.where(person => person.Age > 18);
二,查询表达式操作符
2.1 From 与 Select
2.1.1 语法: from element select source, 以from开头,以select结尾,其中 element 只是一个标示符,select子句被称为投影。
1 // 查询表达式 2 var query = from user in SampleData.Allusers 3 select user; 4 5 // 编译器转换后的点标记 6 var query = SampleData.Allusers.Select(user => user);
2.1.2 退化的查询表达式
当select子句什么都不做时会发生什么?答:编译器依然会生成select方法的调用。
1 from item in SampleData.AllUsers select item;// 编译器: SampleData.Allusers.Select(item=>item); 这就是退化查询表达式
然而,这个表达式和简单表达式Sample.Allusers还是有很大不同的,虽说他们返回的数据项时相同的,不同的是查询表达式返回的是一个新的结果集,对结果集顺序改变不会改变原数据集顺序,但如果改变数据项,则原数据也会改变。
1 var query = from word in Keywords 2 orderby word.Length 3 select word; 4 foreach (var item in query) 5 { 6 Console.WriteLine(item); 7 } 8 9 foreach (var item in Keywords) 10 { 11 Console.WriteLine(item); 12 } 13 14 List<string> target = query.toList();
// IEnumerable<T>.ToList方法为深拷贝。原型如下:
15 //public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) 16 //{ 17 // if (source == null) 18 // { 19 // throw Error.ArgumentNull("source"); 20 // } 21 // return new List<TSource>(source); 22 //}
2.2 Where和OrderBy
2.2.1 OrderBy:ascending升序,descending降序 或者 OrderByDescending。OrderBy如果要继续查询需跟随ThenBy,如若存在多个OrderBy,则最后一个OrderBy“获胜”。
2.2.2 Where:
1 // 查询表达式 2 var query = from c in cc 3 where c.Length == 7 4 where c.Name == Stephen 5 select c.Name; 6 // 查询表达式被转换为 7 cc.where(c=>c.Length ==7).where(c=>c.Name == Stephen);
2.3 let子句
消除不必要的重复“调用”
// 坏代码:调用了两次Name。Length var query = from user in Users orderby user.Name.Length select user.Name; foreach(var name in query) { Console.WriteLine("{0}:{1}",name.Length,name); } // 改造后的代码 var queryNew = from user in Users let len = user.Name.Length orderby len select new {Name = user.Name,Length = len}; foreach(var entry in queryNew) { Console.WriteLine("{0}:{1}",entry.Name,entry.Length); }
2.4 联接join
2.4.1 定义:他使用两张数据表(视图,表值函数等),通过匹配两者之间的数据行来创建结果。
左边序列进行“流处理“,右边序列进行缓冲处理。
1 var query = from a in Sample.aa 2 join b in Sample.bb 3 on a.Name equals b.Name 4 select new { a.Name,b.Name};
2.4.2 join子句的内连接
1 // 有过滤的内联 2 var query = from defect in Sample.AllDefects 3 where defect.Status == Status.Closed 4 join subscription in 5 (from sub in Sample.AllSubscriptions 6 where sub.Status == Status.Open 7 select sub) 8 on defect.Name equals subscription.Name 9 select new { defect.Summary, subscription.EmailAddress };
2.4.3 使用join...into 子句进行分组联接
1 var query = from defect in SampleData.AllDefects 2 join subscription in SampleData.AllSubscriptions 3 on defect.Project equals subscription.Project 4 into groupedSubscriptions 5 select new { Defect=defect, Subscriptions=groupedSubscriptions }; 6 7 foreach (var entry in query) 8 { 9 Console.WriteLine(entry.Defect.Summary); 10 foreach (var subscription in entry.Subscriptions) 11 { 12 Console.WriteLine (" {0}", subscription.EmailAddress); 13 } 14 }
2.5 分组和查询延续
2.5.1 语法: group 投影 by 分组
1 // 分组 2 var query = from defect in SampleData.AllDefects 3 where defect.AssignedTo != null 4 group defect by defect.AssignedTo; 5 6 foreach (var entry in query) 7 { 8 Console.WriteLine(entry.Key.Name); 9 foreach (var defect in entry) 10 { 11 Console.WriteLine(" ({0}) {1}", 12 defect.Severity, 13 defect.Summary); 14 } 15 Console.WriteLine(); 16 } 17 ... 18 // 查询延续 19 var query = from defect in SampleData.AllDefects 20 where defect.AssignedTo != null 21 group defect by defect.AssignedTo into grouped 22 select new { Assignee = grouped.Key, Count = grouped.Count() }; 23 24 foreach (var entry in query) 25 { 26 Console.WriteLine("{0}: {1}", 27 entry.Assignee.Name, 28 entry.Count); 29 }
后记:本文前半部分介绍了一些需要注意的细节上,后半部分简要介绍了一些关键字的用法,并不是十分详细。如果想了解更多MSDN是一个不错的选择(http://msdn.microsoft.com/zh-SG/library/bb384065(v=vs.90).aspx)。
如果本文有任何错误或不尽如人意的地方,还请您不吝赐教。
作者:Stephen Cui
出处:http://www.cnblogs.com/cuiyansong
版权声明:文章属于本人及博客园共有,凡是没有标注[转载]的,请在文章末尾加入我的博客地址。
如果您觉得文章写的还不错,请点击“推荐一下”,谢谢。