• 十五、C# 使用查询表达式的LINQ


    使用查询表达式的LINQ
     
    本章介绍了一种新的语法,查询表达式。
     
    1、查询表达式概述
    2、特点:投射  筛选  排序   Let  分组
    3、作为方法调用
     
    标准查询运算符所实现的查询在功能上与SQL中实现的查询非常相似
     
    C#3.0中添加了一个新的语法:查询表达式。
     
    本章将介绍新的查询表达式语法,并利用这个语法对上一章的许多查询进行表示。
     
    一、查询表达式概述
    除了遍历集合中的所有项之外,开发者经常要执行的另一个操作是对集合进行筛选,目的是
    最终只需要遍历数量较少的项,或者对集合进行投射,使其中的项变成另一个形式。
    如:
    有一个文件集合,可以在垂直方向上筛选它,使结果集合仅包含.cs文件,或者仅包含10个最大的文件。
    除此之外,还可以在“水平”方向对文件集合进行“投射”,使新集合只包含两项数据:文件的目录路径以及目录的大小。
     
    查询表达式一般以一个"from子句"开始,以及一个"select子句"或者"groupby子句"结束。
    每个子句都分别用上下文关键字from、select或group来标识。
     1             string[] KeyWords = { "12", "c*d", "xxm","2*","ab" };
     2  
     3             IEnumerable<string> selection = from word in KeyWords
     4                                             where !word.Contains('*')
     5                                             select word;
     6  
     7             foreach (string word in selection)
     8             {
     9                 Console.WriteLine(word);
    10             }
     
    在这个查询表达式中,我们将一个C#关键字集合赋给selection,集合中排除了上下文关键字(含有*的关键字)
     
    C#查询表达式以上下文关键字from开始。
    之所以要采用这个设计,目的是为了方便实现智能感知功能。
    如以上的代码:由于最开始出现的是from,并将字符串数组KeyWords指定为数据源,所以代码编辑器知道wrod是string类型。
    这样一来,IDE的智能感知功能就可以立即发挥作用----在word之后输入一个成员访问运算符,将只显示出string的成员。
    相反,如果from子句出现在select之后,那么from子句之前的任何加点运算符都无法确定word的数据类型是什么。
    所以就无法显式word的成员列表。
    这里的wrod称为范围变量,它代表集合中的每一项。
     
    1、投射
    查询表达式输出的是一个IEnumerable<T>或IQuerable<T>集合。T的数据类型是从select或groupby子句推导的。
     
    用表达式来一个特定类型的集合时,结果并非一定是原始类型。
    而select子句允许将数据投射成一个完全不同的类型。
     
    1             IEnumerable<FileInfo> files = from fileName in Directory.GetFiles("D:\")
    2                                           select new FileInfo(fileName);
    3  
    4             foreach (FileInfo file in files)
    5             {
    6                 Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
    7             }
    输出:
    bssndrpt.lg(2014/10/8 23:06:06)
    cache_index.db(2015/1/18 18:13:10)
    league of legends.lg(2014/11/30 21:05:52)
    QQ图片20140919173746.jpg(2014/9/19 17:37:50)
     
    注:
    本身fileName是一个string类型,但是经过处理,返回的是FileInfo类型。
    最后返回的是一个IEnumerable<FileInfo>,而不是IEnumerable<string>数据类型。
     
    事实上,C#3.0之所以要引入匿名类型,在很大程序上就是为了利用像这样的"投射"功能。
    通过匿名类型,可以在不定义一个显式类型的前提下选择符合自己要求的数据。
     
     1             var files = from fileName in Directory.GetFiles("D:\")
     2                         select new
     3                         {
     4                             Name = fileName,
     5                             LastWriteTime = new FileInfo(fileName).LastAccessTime
     6                         };
     7  
     8             foreach (var file in files)
     9             {
    10                 Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
    11             }
    假如数据量非常大,而且检索这些数据的代价非常高,那么像这样在"水平"方向上进行投射,
    从而减少与集合中的每一项关联的的数据量,就可以发挥非常积极的作用。
    执行一个查询时,不是获取全部数据,而是通过匿名类型,只在集合中存储或获取需要的数据。
    如果没有匿名类型,开发人员要么使用含有不需要的信息的对象,要么定义一些小的、特定的类,
    这些类只用于存储需要的特定数据。
    而匿名类型允许由编译器(动态)定义类型。在这种类型中,只包含当前情况所需要的数据。
    在其他情况下,则可以采用不同的方式进行投射。
     
    "推迟执行"同样适用于查询表达式。
    赋值本身不会执行查询表达式。
    from子句会在赋值时执行,但是,除非代码开始遍历selection的值,否则,无论投射、筛选,还是from子句之后的一切都不会执行。
     
    selection同时扮演了查询和集合两个角色。
    更多,待查。
     
    2、筛选
     
    使用where 子句在"垂直"方向上筛选集合,结果集合中将包含较少的项(每个数据项都相当于数据表中的一行记录)。
    筛选条件(filter criteria0是用一个断言来表示的。
    所谓断言,本质上就是返回布尔值的一个Lambda表达式。比如word.Contains()
     1  
     2            var files = from fileName in Directory.GetFiles("D:\")
     3                         where File.GetLastWriteTime(fileName).Year<2015
     4                         select new
     5                         {
     6                             Name = fileName,
     7                             LastWriteTime = new FileInfo(fileName).LastAccessTime
     8                         };
     9  
    10             foreach (var file in files)
    11             {
    12                 Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
    13             }
     
    3、排序
    在查询表达式中对数据项进行排序的是orderby子句。
     1             var files = from fileName in Directory.GetFiles("D:\")
     2                         where File.GetLastWriteTime(fileName).Year<2015
     3                         orderby (new FileInfo(fileName)).Length descending,fileName
     4                         select new
     5                         {
     6                             Name = fileName,
     7                             LastWriteTime = new FileInfo(fileName).LastAccessTime
     8                         };
     9  
    10             foreach (var file in files)
    11             {
    12                 Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
    13             }
     
    首先按文件长度降序排序,然后按文件名升序排序。多个排序条件以逗号分隔。
     
    4、let子句
    上一个问题大于,为了求值文件升序,在orderby子句和select子句中都要有FileInfo的一个实例。
     
    1             var files = from fileName in Directory.GetFiles("D:\")
    2                         where File.GetLastWriteTime(fileName).Year < 2015
    3                         let file = new FileInfo(fileName)
    4                         orderby file.Length descending, fileName
    5                         select new
    6                         {
    7                             Name = fileName,
    8                             LastWriteTime = file.LastAccessTime
    9                         };
    用let子句添加的表达式可以在整个查询表达式的范围内使用,为了添加第二个let表达式,只需把它作为一个附加的子句,
    放在第一个from之后和最后一个select/groupby子句之前便可。无需要任何分隔符来分隔表达式。
     
    5、分组
     
    在SQL中,这通常涉及将数据项聚合成一个汇总的header或total---称为一个聚合值。
    然而,C#的表达力更强一些。
    除了提供与每个分组有关的聚合信息,查询表达式还允许组内单独的项构成一系列子集合,父
    列表中的每个项都对应这样的一个子集合。
     
     1             string[] KeyWords = { "12", "c*d", "xxm", "2*", "ab" };
     2  
     3             IEnumerable<IGrouping<bool, string>> selection =
     4                 from word in KeyWords
     5                 group word by word.Contains('*');
     6  
     7             //分成了两组,每一组都是一个 IGrouping<bool, string> 类型
     8             foreach (IGrouping<bool, string> wordGroup in selection)
     9             {
    10                 Console.WriteLine(Environment.NewLine + "{0}", wordGroup.Key ? "关键字" : "非关键字");
    11  
    12                 foreach (string word in wordGroup)
    13                 {
    14                     Console.WriteLine(word);
    15                 }
    16             }
    输出:
     
    非关键字
    12
    xxm
    ab
     
    关键字
    c*d
    2*
     
     
     
    首先,列表中的每一项都是IGrouping<bool,string>类型。
    IGrouping<TKey,TElement>的类型是由group和by后面的数据类型分别决定的。
    也就是说TElement之所以是string,是因为word是一个string。TKey的类型则是由by后面的
    数据类型来决定的。在本例中,word.Contains()返回一个Boolean,所以TKey是一个bool。
     
    group by子句的第二个特点在于,它允许我们使用一个嵌套的foreach循环,以便遍历前面提到的集合。
    如上例,行打印关键字的类型作为标题,再循环打印出其中分组中的每个关键字。
     
    第三,可以在group by子句的末尾附加一个select子句,从而实现"投射"功能。
    从广义上说,select子句是通过"查询延续"机制来附加的---任何查询主体都可以附加到其他查询主体
    的后面,这称为"查询延续"(query continuation)。
     
     
     1             string[] KeyWords = { "12", "c*d", "xxm", "2*", "ab" };
     2  
     3             IEnumerable<IGrouping<bool, string>> keywordGroups =
     4                 from word in KeyWords
     5                 group word by word.Contains('*');
     6  
     7  
     8             var selection = from groups in keywordGroups
     9                             select new
    10                             {
    11                                 IsContextualKeyword = groups.Key,
    12                                 Items = groups
    13                             };
    14  
    15             //分成了两组,每一组都是一个
    16             //{
    17             //    IsContextualKeyword = groups.Key,
    18             //    Items = groups
    19             //};类型
    20             foreach (var wordGroup in selection)
    21             {
    22                 Console.WriteLine(Environment.NewLine + "{0}", wordGroup.IsContextualKeyword ? "关键字" : "非关键字");
    23  
    24                 foreach (string word in wordGroup.Items)
    25                 {
    26                     Console.WriteLine(word);
    27                 }
    28             }
     
    输出:
     
    非关键字
    12
    xxm
    ab
     
    关键字
    c*d
    2*
     
     
    group by 子返回由IGrouping<TKey,TElement>对象构成的一个集合---这和14章讲过的标准查询运算符GroupBy()是一样的。
     
     
    6、使用into进行查询延续
     
    group by 查询后面是第二个从分组中投射出一个匿名类型的查询。
    此时不是写一个额外的查询,而是直接使用上下文关键字into,通过一个查询延
    续子句来扩展查询。
    into允许用一个范围变量来命名group by子句返回的每个数据项。
    into子句是作为附加查询命令的一个生成器来使用的。
     
     1             string[] KeyWords = { "12", "c*d", "xxm", "2*", "ab" };
     2  
     3             var selection =
     4                 from word in KeyWords
     5                 group word by word.Contains('*')
     6                     into groups//范围变量,代表group by子句返回的每个数据项
     7                     select new
     8                     {
     9                         IsContextualKeyword = groups.Key,
    10                         Items = groups
    11                     };
    12  
    13  
    14          
    15  
    16             //分成了两组,每一组都是一个
    17             //{
    18             //    IsContextualKeyword = groups.Key,
    19             //    Items = groups
    20             //};类型
    21             foreach (var wordGroup in selection)
    22             {
    23                 Console.WriteLine(Environment.NewLine + "{0}", wordGroup.IsContextualKeyword ? "关键字" : "非关键字");
    24  
    25                 foreach (string word in wordGroup.Items)
    26                 {
    27                     Console.WriteLine(word);
    28                 }
    29             }
    30  
    使用into 在现有查询结果上运行附加的查询,这不是group by子句特有的一个功能,
    而是所有查询表达式的一个功能。查询延续提供了一种简化的编程手段,它替代了写多个单独
    的查询表达式的形式。
    into避免了利用第一个查询的结果来写第二个查询,它可作为一个管道运算符使用,将第一个查询的结果同
    第二个查询的结果合并到一起。
     
    不重复的成员:
    查询表达式没有专门为不重复的成员规定一个语法,但是可以通过查询运算符Distinct()来实现。
    1             var selection = (
    2                 from word in KeyWords
    3                 select word
    4                     ).Distinct();
     
    查询表达式的编译:
    查询表达式其实是对底层API的一系列方法调用。
    如where 表达式会转换成对System.Linq.Enumerable的 Where()扩展方法的调用。
    where子句指定的条件就像14章描述的那样,成为由Where() 方法指定的条件。
     
    实现隐式执行:
    将选择条件保存到selection中,而不在赋值的时候就执行查询,这个功能是通过委托来实现的。
    编译器将查询表达式转换成在目标上调用的方法,它获取委托作为参数。
    委托是一种特殊的对象,它保存的信息规定了在委托被调用时要执行什么代码。
    由于委托只包含与要执行的东西有关的数据,所以可以保存下来,直到以后执行时才拿出来使用。
     
    如果集合实现了IQueryable<T>(LINQ providers),Lambda表达式会转换成表达式树。
    表达式树是一种层次化的数据结构,它递归地分解成子表达式。
    表达式树通常可以被枚举,然后在另一种语言(比如SQL)中重建为原始的表达式树。
     
    二、查询表达式作为方法调用
     
    CLR和IL并不需要对查询表达式进行任何实现。相反,是由C#编译器将查询表达式转换成方法调用。
     
    扩展方法和Lambda表达式的组合,构成了查询表达式提供的功能的一个超集。
    所有查询表达式都能转换成方法调用。而方法表达式并非问题能转换成查询表达式。
    平时应该尽可能地使用查询表达式,但偶尔也要依赖于方法调用。
    无论如何,一个复杂的查询通常都有必要分解成多个语句,甚至分解成多个方法。
     
  • 相关阅读:
    HDU 2955 Robberies(01背包)
    HDU 2602 Bone Collector(01背包)
    HUST 1352 Repetitions of Substrings(字符串)
    HUST 1358 Uiwurerirexb jeqvad(模拟解密)
    HUST 1404 Hamming Distance(字符串)
    HDU 4520 小Q系列故事――最佳裁判(STL)
    HDU 2058 The sum problem(枚举)
    【破解】修改程序版权、添加弹窗
    HDU 1407 测试你是否和LTC水平一样高(枚举)
    HDU 1050 Moving Tables(贪心)
  • 原文地址:https://www.cnblogs.com/tlxxm/p/4674997.html
Copyright © 2020-2023  润新知