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)。

      如果本文有任何错误或不尽如人意的地方,还请您不吝赐教。

 

 

 

 

 

posted @ 2013-06-04 11:09  史蒂芬King  阅读(965)  评论(0编辑  收藏  举报