zoukankan      html  css  js  c++  java
  • 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)。

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

  • 相关阅读:
    【Swift学习】Swift编程之旅---可选链(二十一)
    【Swift学习】Swift编程之旅---ARC(二十)
    Swift 3.0首个开发者预览版将在5月12日释出
    【Swift学习】Swift编程之旅---析构方法(十九)
    【Swift学习】Swift编程之旅---构造方法(十八)
    【Swift学习】Swift编程之旅---继承(十七)
    swift3.0的改变
    【Swift学习】Swift编程之旅---方法(十五)
    【Swift学习】Swift编程之旅---Subscripts下标(十六)
    【Swift学习】Swift编程之旅---属性(十四)
  • 原文地址:https://www.cnblogs.com/cuiyansong/p/3107490.html
Copyright © 2011-2022 走看看