• 十四、C# 支持标准查询运算符的集合接口


    支持标准查询运算符的集合接口。
    System.Linq.Enumeralbe类提供的一些常用的API 来执行集合处理
    1、匿名类型
    2、隐匿类型的局部变量
    3、集合初始化器
    4、集合
    5、标准查询运算符
     
    本章主要讨论泛型集合接口。
    非泛型的集合类,待查。
     
    一、匿名类型和隐式类型的局部变量声明
    C#3.0增强。
    1、匿名类型
    一种特殊的数据类型,它最终是由编译器声明的,而非通过已定义好的类来声明的。
    和匿名函数相似,当编译器看到一个匿名类型时,会自动执行一些后台操作,生成必要的代码,
    允许像显式声明的那样使用它。
     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             var patent1 = new
     6             {
     7                 Title = "xxm1",
     8                 YearOfPublication = "1977"
     9             };
    10             var patent2 = new
    11             {
    12                 Title = "xxm2",
    13                 YearOfPublication = "1978"
    14             };
    15             var patent3 = new
    16             {
    17                 patent1.Title,
    18                 //重新命名属性
    19                 Year = patent2.YearOfPublication
    20             };
    21  
    22             Console.WriteLine(patent1.Title + ":" + patent1.YearOfPublication);
    23             Console.WriteLine(patent2.Title + ":" + patent2.YearOfPublication);
    24             Console.WriteLine(patent3.Title + ":" + patent3.Year);
    25  
    26             Console.WriteLine();
    27             Console.WriteLine(patent1);
    28             Console.WriteLine(patent2);
    29             Console.WriteLine(patent3);
    30  
    31             Console.ReadLine();
    32  
    33  
    34  
    35         }
    36     }
    37  
    38 输出:
    39 xxm1:1977
    40 xxm2:1978
    41 xxm1:1978
    42  
    43 { Title = xxm1, YearOfPublication = 1977 }
    44 { Title = xxm2, YearOfPublication = 1978 }
    45 { Title = xxm1, Year = 1978 }
     
     
    匿名类型完全是由C#编译器实现的,而不会在"运行时"内有显式实现。
    具体地说,当编译器遇到匿名类型的语法时,会自动生成一个CIL类,
    其属性和匿名类型声明中命名的值和数据类型是对应的。
     
    2、隐式类型的局部变量
    由于根据定义,匿名类型是没有名称的,所以不可能将一个局部变量显式声明为匿名类型。
    相反,局部变量的类型要替换成var。
    假如将一个匿名类型赋给一个隐式类型的变量,那么在为局部变量生成的CIL代码中,它的数据类型就是
    编译器生成的类型,类似地,如果将一个string赋给隐式类型的变量,那么在最终生成的CIL中,它的数据类型就是
    string。事实上,对于隐式类型的变量来说,假如赋给它的是一个非匿名的类型如:string,那么最终生成的CIL代码和
    直接声明为string类型并无区别。
     
    1 string text=" this is a test of the ...";
    2 //<====>
    3 var text="this is a test of the ...";
    这两个语句最终生成的CIL代码是完全一样的。
     
    虽然C#的匿名类型没有可用的名称,但它仍然是强类型的。
    比如:类型的属性是完全 可以访问的。
    对于匿名类型来说,是不可能指定数据类型的,所以必须使用var。
     
    3、匿名类型和隐式局部变量的更多注意事项
    在匿名类型进行声明时,如果所赋值的是一个属性或者字段调用,名称就无需要指定(也可以指定)。
    1             var patent3 = new
    2             {
    3                 patent1.Title,
    4                 //重新命名属性
    5                 Year = patent2.YearOfPublication
    6             };
     
    如果两个匿名类型的属性名称和顺序以及数据类型都完全匹配的话,系统在编译时只为这两个
    匿名类型声明生成一个数据类型。
     
    所以,只有属性名、数据类型和属性顺序完全匹配,才类型兼容。
     
    匿名类型是不可变的,一经实例化,再更改它的某个属性,会生成编译错误。
     
    在声明一个方法时,不可能装饰它的某个参数声明为隐式数据类型(var)。
    在创建匿名类型的那个方法的内部,只能以及两种方式将匿名类型的实例传到方法外部。
    首先,如果方法的参数是object类型,则匿名类型的实例可以传到方法外部,因为匿名类型会隐匿地转换。
    第二种方式是使用方法类型推导,在这种情况下,匿名类型的实例以一个方法的“类型参数”的形式来传递,
    编译器能成功推导出具体的类型。
     
    所以,使用Function(patent1)调用void Method<T>(T parameter)会成功地通过编译,尽管在Function()内部,
    parameter允许的操作仅限于object支持的那些操作。
     
    匿名类型是C#3.0支持“投射”的关键。
     
    匿名类型的生成:
    虽然Console.WriteLine(patent1) 默认调用了ToString(),但匿名类型的ToString()已经得到重写,
    编译器在生成匿名类型的代码时,重写了ToString()方法。类似地,在生成的类型中也重要了Equals()和GetHashCode()的实现。
    所以,一旦属性的顺序发生了变化,就会生成最终生成一个全新的数据类型。
    假如不是这样设置,而是为属性顺序不同的两个匿名类型生成同一个类型,
    那么一个实现在属性顺序上发生的变化,就会对另一个实现的ToString()输出造成显式 的、甚至可能是让人不可接受的影响。
     
    除此之外,在程序执行的时候,有可能反射一个类型,并检查类型的成员-----甚至动态调用其中一个成员(所谓“动态调用成员 ”,是指在
    程序运行期间,根据具体的情况来决定调用哪个成员)。如果两个貌似相同的类型在成员顺序上有所区别,就可能造成出乎预料的结果。
    为了避免这些问题,C#的设计者最终决定:假如属性的顺序不同,就生成两个不同的类型。
     
    二、集合初始化器
    C#3.0新增的另一个特性是集合初始化器。
    使用集合初始化器,程序员可以采取和数组声明相似的方式,在一个集合的实例化期间用一套初始的成员来构造这个集合。
    如果没有集合初始化器,就只有在集合实例化好之后,才能将成员显式 添加到集合中。
     
     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5  
     6             List<string> sevenWorldBlunders = new List<string>();
     7             sevenWorldBlunders = new List<string>()
     8             {
     9                 "Wealth without work",
    10                 "Pleasure without conscience",
    11                 "Knowledge without character",
    12             };
    13  
    14  
    15             Print(sevenWorldBlunders);
    16  
    17         }
    18         private static void Print<T>(IEnumerable<T> items)
    19         {
    20             foreach (T item in items)
    21             {
    22                 Console.WriteLine(item);
    23             }
    24  
    25         }
    26     }
     
    这个语法不仅和数组初始化的语法相似,也和对象初始化器语法相似。
     
    集合初始化器要想成功编译,需满足几个基本条件。理想情况下,集合类型应该实现了
    System.Collections.Generic.ICollection<T>接口。
    或者在实现了IEnumerable<T>的类型上存在一个或多个Add方法。
     
    匿名类型不可以使用集合初始化器。有几种解决方法。待查。
     
    三、是什么使用类成为一个集合:IEnumberable<T>
    按照定义,.NET中的一个集合本质上是一个类,它最起码实现了IEnumerable<T>(从技术角度说应该是非泛型类型
    IEnumberable。
    要想支持对集合执行遍历,最起码的要求就是实现由IEnumberable<T>规定的方法。
     注:“运行时”根本不知foreach语句为何物。
    1、foreach和数组
    1             int[] arr = new[] { 1, 2, 3, 4, 5 };
    2  
    3             foreach (int item in arr)
    4             {
    5                 Console.WriteLine(item);
    6             }
     
    基于这段代码,C#编译器会用CIL来创建一个等价的for循环。
    foreach在这个例子中,要依赖于Length属性和数组索引运算符[]的支持。
     
    2、foreach和IEnumerable<T>
     
    不是所有类型的集合都包含已知数量的元素。
    除此之外,许多集合类,包括Stack<T>、Queue<T>以及 Dictionary<Tkey,Tvalue>,都不支持按照索引来获取元素。
    因此,需要一种更常规的方式来遍历元素集合。迭代器(iterator)模式提供了这个能力。
    只要你能确定第一个元素、下一个元素和最后一个元素,就不需要事先知道元素总数,也不需要按照索引来获取元素。
     
    System.Collections.Generic.IEnumerator<T>和非泛型System.Collections.Generic.IEnumerator接口的设计目标就是允许用
    迭代器模式来遍历元素集合,同时放弃使用:以上使用的长度---索引(length---index)模式。
     
    IEnumerator<T>从IEnumerator派生,后者包含3个成员。
    第一个成员是bool MoveNext()。
    第二个成员是只读属性Current(返回当前元素。
    利用这两个成员,只需要用一个while循环就可以遍历集合。
     
    但是有两个重要的实现细节:交错和错误处理
     
    2.1 状态共享
    假如同时有两个循环交错遍历同一个集合(一个foreach中嵌套了另一个foreach),
    那么集合必须维持当前元素的一个状态指示器,确保当调用MoveNext()时,能正确定位下一个元素。
    现在的问题是,交错的循环可能相互干扰(假如循环由多个线程执行,那么会发生同样的问题)。
     
    为了解决这个问题,集合类不直接支持IEnumerator<T>和IEnumerator接口。
    还有第二个接口,它名为IEnumerable<T>,它唯一的方法就是GetEnumerator()。
    这个方法的作用是返回支持IEnumerator<T>的一个对象。在这里,不是由集合类维持状态。
    相反,是由一个不同的类(通常是一个嵌套类,以便访问到集合内部)来支持IEnumerator<T>接口,
    并负责维护循环遍历的状态。
    枚举数相当于一个"游标“或者”书签“。
    可以有多个书签,移动每个书签 都可独立于其他书签来遍历集合。
     
    2.2 清理状态
    由于是由实现了IEnumerator<T>接口的类来维持状态,所以在退出循环之后,有时需要对状态进行清理。
    为此IEnumerator<T>接口派生于IDisposable。实现IEnumerator的枚举数不一定要实现IDisposable。
    但是,假如实现了IDisposable,就一样会调用Dispose()方法。
     
    没有IEnumerable的foreach:
    从技术上说,编译器为了用foreach在一个数据类型上迭代,并不要求一定要支持IEnumerator<T>/IEnumerator。
    相反,编译器采用一个称为"Duck typing"的概念。
    Duck typing待查。
     
    3、foreach循环内不要修改集合
     
    第3章讲过,编译器禁止对foreach变量标识符进行赋值。
     
    假如在foreach循环期间对集合进行了修改,重新访问枚举数就会引发System.
    InvalidOperationException类型的异常,指出在枚举数实例化之后,集合已经发生了变化。
    迭代器也会受影响。
     
    四、标准查询运算符
     
    如果将System.Object定义的方法排除在外,那么实现IEnumerable<T>的任何类型都只有一个方法,
    即GetEnumerator()。
    事实上,任何类型在实现IEnumerable<T>之后,都有超过50个方法可供使用。
    其中还不包括重载的版本。
    只需要显式实现接口中的GetEnumerator()方法。
    其它的附加的功能是由C#3.0的扩展方法来提供的。
    所有方法都在System.Linq.Enumerable类中定义。
    所以,为了用到这些方法,只需要简单地添加以下语句:
    using System.Linq;
     
    IEnumerable<T>上的每个方法都是一个标准查询运算符(standard query operator)。
    它提供了对它操作的集合进行查询的能力。
     
    示例类:
      1     class Program
      2     {
      3         static void Main(string[] args)
      4         {
      5             IEnumerable<Patent> patents = PatentData.Patents;
      6  
      7             Print(patents);
      8  
      9             Console.WriteLine();
     10  
     11             IEnumerable<Inventor> inventors = PatentData.Inventors;
     12  
     13             Print(inventors);
     14  
     15             Console.ReadLine();
     16  
     17  
     18  
     19  
     20         }
     21         public static void Print<T>(IEnumerable<T> items)
     22         {
     23             foreach (T item in items)
     24             {
     25                 Console.WriteLine(item);
     26             }
     27         }
     28         //专利类
     29         public class Patent
     30         {
     31             public string Title { get; set; }
     32  
     33             public string YearOfPublication { get; set; }
     34             public string ApplicationNumber { get; set; }
     35             public long[] InventorIds { get; set; }
     36             public override string ToString()
     37             {
     38                 return string.Format("{0}({1})", Title, YearOfPublication);
     39             }
     40         }
     41         //发明者类
     42         public class Inventor
     43         {
     44             public long Id { get; set; }
     45             public string Name { get; set; }
     46             public string City { get; set; }
     47             public string State { get; set; }
     48             public string Country { get; set; }
     49             public override string ToString()
     50             {
     51                 return string.Format("{0}({1},{2})", Name, City, State);
     52             }
     53         }
     54  
     55         //实际数据
     56         public static class PatentData
     57         {
     58             public static readonly Inventor[] Inventors = new Inventor[] {
     59                 new Inventor(){
     60                     Name="Benjamin Franklin",City="Philadelphia",
     61                     State="PA",Country="USA",Id=1
     62                 },
     63                 new Inventor(){
     64                     Name="Orville Wright",City="Kitty Hawk",
     65                     State="NC",Country="USA",Id=2
     66                 },
     67                 new Inventor(){
     68                     Name="Wilbur Wright",City="Kitty Hawk",
     69                     State="NC",Country="USA",Id=3
     70                 },
     71                 new Inventor(){
     72                     Name="Samuel Morse",City="New York",
     73                     State="NY",Country="USA",Id=4
     74                 },
     75                 new Inventor(){
     76                     Name="George Stephenson",City="Wylam",
     77                     State="Northumberland",Country="UK",Id=5
     78                 },
     79                 new Inventor(){
     80                     Name="John Michaelis",City="Chicago",
     81                     State="IL",Country="USA",Id=6
     82                 },
     83                 new Inventor(){
     84                     Name="Mary Phelps Jacob",City="New York",
     85                     State="NY",Country="USA",Id=7
     86                 },
     87             };
     88  
     89             public static readonly Patent[] Patents = new Patent[] {
     90                 new Patent(){
     91                     Title="Bifocals",YearOfPublication="1784",
     92                     InventorIds=new long[]{1}
     93                 },
     94                 new Patent(){
     95                     Title="Phonograph",YearOfPublication="1877",
     96                     InventorIds=new long[]{1}
     97                 },
     98                 new Patent(){
     99                     Title="Kinetoscope",YearOfPublication="1888",
    100                     InventorIds=new long[]{1}
    101                 },
    102                 new Patent(){
    103                     Title="Electrical Telegraph",YearOfPublication="1837",
    104                     InventorIds=new long[]{4}
    105                 },
    106                 new Patent(){
    107                     Title="Flying machine",YearOfPublication="1903",
    108                     InventorIds=new long[]{2,3}
    109                 },
    110                 new Patent(){
    111                     Title="Steam Locomotive",YearOfPublication="1815",
    112                     InventorIds=new long[]{5}
    113                 },
    114                 new Patent(){
    115                     Title="Droplet deposition apparatus",YearOfPublication="1989",
    116                     InventorIds=new long[]{6}
    117                 },
    118                 new Patent(){
    119                     Title="Backless Brassiere",YearOfPublication="1914",
    120                     InventorIds=new long[]{7}
    121                 },
    122             };
    123         }
    124     }
     
    以下的的Lambda表达的形式参数的类型都与集合中的元素类型一致。
    1、使用Where()来筛选
     
    为了从集合中筛选出某些数据,需要提供一个筛选器方法返回true或false,从而表明
    一个特定的元素是否应该被包含进来。
    获取一个实参,并返回一个布尔值的委托表达式称为一个“谓词”。
     
    1             IEnumerable<Patent> patents = PatentData.Patents;
    2  
    3             patents = patents.Where(
    4                 patent => patent.YearOfPublication.StartsWith("18")
    5                 );
    6  
    7             Print(patents);
     
    注意:代码将Where()的输出结果赋还给IEnumerable<T>。
    IEnumerable<T>.Where()输出的是一个新的IEnumerable<T>的集合。以上的代码返回的是IEnumerable<Patent>。
     
    Where()方法的表达式实参并非一定是在赋值时求值的。这一点适用于许多标准查询运算符。
    在Where()的情况下,表达式传给集合,“保存”起来但不马上执行。
    相反,只有在需要遍历集合中的项时,才会真正对表达式进行求值。
     
    应该将Where()方法理解为只是描述了集合中应该出现什么,它没有涉及更实际的工作。
     
    2、使用Select()来投射
    由于IEnumerable<T>.Where()输出的是一个新的
    IEnumerable<T>集合,所以可以在这个集合的基础上再调用另一个标准查询运算符
    使用Select()进行转换。
    1             IEnumerable<Patent> patents = PatentData.Patents;
    2  
    3             IEnumerable<Patent> patents1800 = patents.Where(
    4                 patent => patent.YearOfPublication.StartsWith("18")
    5                 );
    6             IEnumerable<string> items = patents1800.Select(item => item.ToString());
    7  
    8             //Print(patents);
    9             Print(items);
    在此代码中,创建了一个新的IEnumerable<string>集合。虽然添加了一个Select()调用,但并未
    造成输出有任何改变。
    显然,针对每一个数据项,都会发生一次转换:从原始集合的Patent类型转换成items集合的string类型。
     
     1             IEnumerable<string> filelist = Directory.GetFiles("D:\");
     2             IEnumerable<FileInfo> files = filelist.Select(file => new FileInfo(file));
     3             Print(files);
     4             //注:以上的Lambda表达的形式参数的类型都与集合中的元素类型一致。
     5 匿名类型:
     6             IEnumerable<string> filelist = Directory.GetFiles("D:\");
     7             var items = filelist.Select(file =>
     8             {
     9                 FileInfo fileInfo = new FileInfo(file);
    10                 return new { FileName = fileInfo.Name, Size = fileInfo.Length };
    11             });
    12             Print(items);
     
    使用Where()标准查询运算符,在“垂直”方向上筛选一个集合(减少集合项目中元素的数量)。
    现在使用Select()标准查询运算符,还可以在“水平”方向上缩减集合的规模(减少列的数量)或者对数据进行彻底的转换。
     
    综合运用Where()和Select(),可以获得原始集合的一个子集,从而满足当前算法的要求。
     
    LINQ查询的并行运行:
    修改程序支持多线程。
    1             IEnumerable<string> filelist = Directory.GetFiles("D:\");
    2             var items = filelist.AsParallel().Select(file =>
    3             {
    4                 FileInfo fileInfo = new FileInfo(file);
    5                 return new { FileName = fileInfo.Name, Size = fileInfo.Length };
    6             });
    7             Print(items);
     
    代码中发生的变化使并行 支持变得轻而易举。
    唯一要做就是利用.NET Framework 4引入的标准查询运算符AsParallel()。
    这是静态类System.Linq.ParallelEnumerable的一个成员。
    使用这个简单的扩展方法,“运行时”一边遍历fileList中的数据项,一边返回结果对象,这两个操作是并行发生的。
     
    3、用Count()对元素进行计数。
     
    使用Count()来统计所有元素的数量,或者获取一个谓词作为参数,只对谓词表达式指明的数据项进行计数。
     
    1             IEnumerable<Patent> patents = PatentData.Patents;
    2             Console.WriteLine("Patent Count:{0}", patents.Count());
    3             Console.WriteLine("Patent Count in 1800s:{0}",
    4                 patents.Count(
    5                 patent => patent.YearOfPublication.StartsWith("18")
    6                 ));
     
    虽然Count()语句写起来很简单,但IEnumerable<T>没有改变,所以真正执行的代码仍然会遍历集合中的所有项。
    如果集合直接提供了一个Count属性,就应首选这个属性,而不要用LINQ的Count()方法。
    幸好,ICollection<T>包含了Count属性,所以如果一个集合支持ICollection<T>,那么在它上面调用Count()方法,会对集合
    进行转型,并直接调用Count。然后,如果不支持ICollection<T>,Enumerable.Count()就会枚举集合中的所有项,而不是调用内
    建的Count机制。
    如果计数的目的只是为了看这个计数是否大于0,那么首选的做法是使用Any()运算符
    1 if(patents.Any())
    2 {  }
    Any()只尝试遍历集合中的一个项,如果成功就返回true。它不会遍历整个序列。
     
    4、推迟执行
    使用LINQ时,一个重要概念就是推迟执行。
     1             IEnumerable<Patent> patents = PatentData.Patents;
     2             bool result;
     3             patents = patents.Where(patent =>
     4                 {
     5                     if (result = patent.YearOfPublication.StartsWith("18"))
     6                     {
     7                         Console.WriteLine("Where StartsWith 18 :" + patent);
     8                     }
     9                     return result;
    10                 });
    11             Console.WriteLine("1. Patents prior to the 1900s are:");
    12             foreach (Patent patent in patents)
    13             {
    14  
    15             }
    16             Console.WriteLine();
    17             Console.WriteLine("2. A second listing of patents prior to the 1900s:");
    18             Console.WriteLine("   There are {0} patents prior to 1900.", patents.Count());
    19  
    20             Console.WriteLine();
    21             Console.WriteLine("3. A third listing of patents prior to the 1900s:");
    22             patents = patents.ToArray();
    23             Console.Write("   There are  ");
    24             Console.WriteLine("{0} patents prior to 1900.", patents.Count());
     
    代码输出:
    1. Patents prior to the 1900s are:
    Where StartsWith 18 :Phonograph(1877)
    Where StartsWith 18 :Kinetoscope(1888)
    Where StartsWith 18 :Electrical Telegraph(1837)
    Where StartsWith 18 :Steam Locomotive(1815)
     
    2. A second listing of patents prior to the 1900s:
    Where StartsWith 18 :Phonograph(1877)
    Where StartsWith 18 :Kinetoscope(1888)
    Where StartsWith 18 :Electrical Telegraph(1837)
    Where StartsWith 18 :Steam Locomotive(1815)
       There are 4 patents prior to 1900.
     
    3. A third listing of patents prior to the 1900s:
    Where StartsWith 18 :Phonograph(1877)
    Where StartsWith 18 :Kinetoscope(1888)
    Where StartsWith 18 :Electrical Telegraph(1837)
    Where StartsWith 18 :Steam Locomotive(1815)
       There are  4 patents prior to 1900.
     
    注意,Console.WriteLine("1. Patents prior...");是先于Lambda表达式执行的。
    这是非常重要的一个特点。
    通常,任何谓词都只应做一件事情:对一个条件进行求值。它不应该有任何“副作用”(比如在本例中打印到控制台)。
     
     
    为了理解这背后发生的事情,请记住Lambda表达式是可以传递到别的地方的委托-----是一个对方法的引用(方法指针)。
    在LINQ和标准查询运算符的上下文中,每个Lambda表达式都构成了要执行的总体查询的一部分。
     
    在声明时,它们是不执行的的。除非调用Lambda表达式,造成其中的代码开始执行,否则Lambda表达式不会执行。
     
    以上代码中的3个调用触发了Lambda表达式的执行,每一次都是隐式触发的。
    假如Lamba表达式执行的代价比较高(比如调用一个数据库),那么为了优化代码,很重要的一点就是尽量减少
    Lambda表达式的执行。
     
    首先:foreach循环内会触发Lambda表达式的执行。foreach循环会被分解成一个MoveNext()调用,而且每个调用都会造成
    原始集合中的每一项执行Lambda表达式。在循环迭代期间,“运行时”会为每一项调用Lambda表达式,以判断该项是否满足谓词。
     
    其次:调用Enumerable的Count()函数,会再次为每一项触发Lambda表达式。这很容易被忽视,因为在没有用标准查询运算符来查询
    的集合上,Count属性是非常常见的。
     
    最后,调用ToArray() (或ToList()、ToDictionary()或ToLookup() )会为每一项触发Lambda表达式。
    使用这些ToXXX方法来转换集合是相当有用的。这样返回的是标准查询运算符已处理过的一个集合。
    这样返回的是标准查询运算符已处理过的一个集合。在以上代码中,转换成一个数组意味着在最后一个Consloe.WriteLine()语句调用
    Length时,patents指向的基础对象事实上是一个数组(它显然实现了IEnumerable<T>),所以调用的length是由System.Array实现的,
    而不是由System.Linq.Enumerable实现的。
    因此,在用一个ToXXX方法转换成一个集合类型之后,一般可以安全地操纵集合(除非调用了另一个标准查询运算符)。但要注意,这会
    造成将整个结果集都加载到内存(在此之前可以驻留在一个数据库或文件中)。除此之外,ToXXX方法会创建基础数据的“快照”,确保
    在重新查询ToXXX方法的结果时,不会返回新的结果。
     
     
    开发者就提高警惕,防止在不知不觉间触发标准查询运算符。
    查询对象代表的是查询,而不是结果。
    向查询要求结果时,整个查询都会执行,因为查询对象不确定结果和上一次执行的结果是不是一样。
     
    注:为了避免这种反复性的执行,一个查询在执行之后,有必要把它获取的数组缓存起来。
    一般情况下,如果“内存中的集合快照”是你所想要的,那么最好的做法就是将查询表达式赋给一个缓存的集合,避免无谓的迭代。
     
    顺序图:待复制 p426 433 与实际代码对照。
     
     
    5、使用OrderBy()和ThenBy()来排序
    进行排序,这涉及对System.Linq.Enumerable的OrderBy()的一个调用。
     
     1             IEnumerable<Patent> items = null;
     2             Patent[] patents = PatentData.Patents;
     3  
     4             items = patents.OrderBy(patent => patent.YearOfPublication)
     5                 .ThenBy(patent => patent.Title);
     6             Print(items);
     7  
     8             Console.WriteLine();
     9  
    10             items = patents.OrderByDescending(patent => patent.YearOfPublication)
    11                 .ThenByDescending(patent => patent.Title);
    12             Print(items);
    13  
    OrderBy()获取一个Lambda表达式,该表达式标识了要据此进行排序的键。
    后续排序需要用到ThenBy()
     
    OrderBy()返回的是一个IOrderedEnumerable<T>接口,而不是一个IEnumerable<T>。除此之外,
    IOrderedEnumerable<T>是从IEnumerable<T>派生的,所以能为OrderBy()的返回值使用所有的标准查询运算符。
    但是,假如重复调用OrderBy(),会撤销上一个OrderBy()的工作。
    为了指定额外的排序条件,应该使用ThenBy()。System.Linq.Extensions.Enumerable上定义的。
    总之,要先使用OrderBy(),再执行零个或者多个对ThenBy()的调用来提供额外的排序"列"。
     
    排序,有两个重要问题:
    首先,要等到开始访问集合中的成员时,才会实际开始排序,在那个时候,整个查询都会被处理。
    显然,除非拿以了所有需要排序的项,否则是无法排序的。如果没有得到所有的项,就无法确定自己是否已经获得了
    第一项。排序被操作性到开始访问成员的时候才开始。
     
    其实,执行后续数据排序调用时,会再次调用之前的keySelector Lambda表达式(orderby中的表达式)。
     
     
     
     
    6、使用Join()来执行内部联接
     
    在客户端上,对象和对象的关系一般已经建立好了。
    但是,从非对象存储加载的数据则一般不是这种情况。
    相反,这些数据需要联接到一起,以便能以一种适合数据的方式,从一种对象类型切换到下一种。
     
      1     class Program
      2     {
      3         static void Main(string[] args)
      4         {
      5  
      6             Department[] departments = CorporateData.Departments;
      7             Employee[] employees = CorporateData.Employees;
      8  
      9             var items = employees.Join(
     10                 departments,//关联的数据
     11                 employee => employee.DepartmentId,//联接时的参考属性
     12                 department => department.Id,//联接时的参考属性
     13                 (employee, department) => new //返回的数据
     14                 {
     15                     employee.Id,
     16                     employee.Name,
     17                     employee.Title,
     18                     Department = department
     19  
     20                 });
     21             Print(items);
     22  
     23             Console.ReadLine();
     24  
     25  
     26  
     27  
     28         }
     29         public static void Print<T>(IEnumerable<T> items)
     30         {
     31             foreach (T item in items)
     32             {
     33                 Console.WriteLine(item);
     34             }
     35         }
     36  
     37  
     38         //部门类
     39         public class Department
     40         {
     41             public long Id { get; set; }
     42             public string Name { get; set; }
     43             public override string ToString()
     44             {
     45                 return string.Format("{0}", Name);
     46             }
     47         }
     48  
     49         //员工类
     50         public class Employee
     51         {
     52             public int Id { get; set; }
     53             public string Name { get; set; }
     54             public string Title { get; set; }
     55             public int DepartmentId { get; set; }
     56             public override string ToString()
     57             {
     58                 return string.Format("{0}({1})", Name, Title);
     59             }
     60         }
     61  
     62         //实际数据
     63         public static class CorporateData
     64         {
     65             public static readonly Department[] Departments = new Department[] {
     66                 new Department()
     67                 {
     68                     Name="Corporate",Id=0
     69                 },
     70                 new Department()
     71                 {
     72                     Name="Corporate",Id=1
     73                 },
     74                 new Department()
     75                 {
     76                     Name="Engineering",Id=2
     77                 },
     78                 new Department()
     79                 {
     80                     Name="Information Technology",Id=3
     81                 },
     82                 new Department()
     83                 {
     84                     Name="Research",Id=4
     85                 },
     86                 new Department()
     87                 {
     88                     Name="Marketing",Id=5
     89                 },
     90             };
     91             public static readonly Employee[] Employees = new Employee[] {
     92                 new Employee()
     93                 {
     94                     Name="Mark Michaelis",
     95                     Title="Chief Computer Nerd",
     96                     DepartmentId=0
     97                 },
     98                 new Employee()
     99                 {
    100                     Name="Michael Stokesbary",
    101                     Title="Senior Computer Wizard",
    102                     DepartmentId=2
    103                 },
    104                 new Employee()
    105                 {
    106                     Name="Brian Jones",
    107                     Title="Enterprise Integration Guru",
    108                     DepartmentId=2
    109                 },
    110                 new Employee()
    111                 {
    112                     Name="Jewel Floch",
    113                     Title="Bookkeeper Extraordinaire",
    114                     DepartmentId=1
    115                 },
    116                 new Employee()
    117                 {
    118                     Name="Robert Stokesbary",
    119                     Title="Expert Mainframe Engineer",
    120                     DepartmentId=3
    121                 },
    122                 new Employee()
    123                 {
    124                     Name="Paul R.Bramsman",
    125                     Title="Programmer Extraordinaire",
    126                     DepartmentId=2
    127                 },
    128                 new Employee()
    129                 {
    130                     Name="Thomas Heavey",
    131                     Title="Software Architect",
    132                     DepartmentId=2
    133                 },
    134                 new Employee()
    135                 {
    136                     Name="John Michaelis",
    137                     Title="Inventor",
    138                     DepartmentId=4
    139                 },
    140  
    141             };
    142         }
    143  
    144     }
    145  
    146 输出:
    147 { Id = 0, Name = Mark Michaelis, Title = Chief Computer Nerd, Department = Corpo
    148 rate }
    149 { Id = 0, Name = Michael Stokesbary, Title = Senior Computer Wizard, Department
    150 = Engineering }
    151 { Id = 0, Name = Brian Jones, Title = Enterprise Integration Guru, Department =
    152 Engineering }
    153 { Id = 0, Name = Jewel Floch, Title = Bookkeeper Extraordinaire, Department = Co
    154 rporate }
    155 { Id = 0, Name = Robert Stokesbary, Title = Expert Mainframe Engineer, Departmen
    156 t = Information Technology }
    157 { Id = 0, Name = Paul R.Bramsman, Title = Programmer Extraordinaire, Department
    158 = Engineering }
    159 { Id = 0, Name = Thomas Heavey, Title = Software Architect, Department = Enginee
    160 ring }
    161 { Id = 0, Name = John Michaelis, Title = Inventor, Department = Research }
     
    Join()的第一个参数是inner,指定了要联接到的目标集合。
    接着两个参数都是Lambda表达式,它们指定了两个集合如何联接。
    最后一个参数也是Lambda表达式,描述如何对结果项进行选取或组合返回。
     
    注:上述使用的是数组,说明实现了该接口IEnumerable
     
    7、使用GroupBy分组结果
    对具有相似特征的对象进行分组。
    如:对于员工数据,可以按部门、地区、职务等对员工进行分组。
     
     1             IEnumerable<Employee> employees = CorporateData.Employees;
     2             IEnumerable<Department> departments = CorporateData.Departments;
     3  
     4             IEnumerable<IGrouping<int, Employee>> groupedEmployees =
     5                 employees.GroupBy(employee => employee.DepartmentId);
     6             foreach (IGrouping<int, Employee> employeeGroup in groupedEmployees)
     7             {
     8                 Console.WriteLine();
     9                 foreach (Employee employee in employeeGroup)
    10                 {
    11                     Console.WriteLine("	"+employee);
    12                 }
    13                 Console.WriteLine("	 Count:"+employeeGroup.Count());
    14             }
    15  
    输出:
     
            Mark Michaelis(Chief Computer Nerd)
             Count:1
     
            Michael Stokesbary(Senior Computer Wizard)
            Brian Jones(Enterprise Integration Guru)
            Paul R.Bramsman(Programmer Extraordinaire)
            Thomas Heavey(Software Architect)
             Count:4
     
            Jewel Floch(Bookkeeper Extraordinaire)
             Count:1
     
            Robert Stokesbary(Expert Mainframe Engineer)
             Count:1
     
            John Michaelis(Inventor)
             Count:1
    GroupBy()返回的是IGrouping<Tkey,TElement>类型的数据项,该类型有一个属性代表
    作为分组依据使用的键。然而,它没有为组中的数据项准备一个属性。
    相反,由于 IGrouping<Tkey,TElement>是从IEnumerable<T>派生的,所以可以用一个foreach
    语句枚举组中的项,或者将数据聚合成像项目计数这样的东西(employeeGroup.Count())。
     
    8、使用GroupJoin(0实现一对多关系
     
    创建一个员工列表,同时显式所属部门,但不在每一项员工数据当中添加,
    而是显示一个部门下属的所有员式的一个集合,而不是为每个部门-员工关系建立一条匿名类型的记录。
     
     1             IEnumerable<Employee> employees = CorporateData.Employees;
     2             IEnumerable<Department> departments = CorporateData.Departments;
     3  
     4  
     5             var items = departments.GroupJoin(
     6                 employees,//关联的对象数组
     7                 department => department.Id,//关联的键
     8                 employee => employee.DepartmentId,//关联的键
     9                 (department, departmentEmployees) => new//返回的数据
    10                 {
    11                     department.Id,
    12                     department.Name,
    13                     Employees = departmentEmployees
    14                 });
    15             foreach (var item in items)
    16             {
    17                 Console.WriteLine("部门:{0}", item.Name+":");
    18                 foreach (Employee employee in item.Employees)
    19                 {
    20                     Console.WriteLine("	"+employee);
    21                 }
    22             }
    输出:
    部门:Corporate:
            Mark Michaelis(Chief Computer Nerd)
    部门:Corporate:
            Jewel Floch(Bookkeeper Extraordinaire)
    部门:Engineering:
            Michael Stokesbary(Senior Computer Wizard)
            Brian Jones(Enterprise Integration Guru)
            Paul R.Bramsman(Programmer Extraordinaire)
            Thomas Heavey(Software Architect)
    部门:Information Technology:
            Robert Stokesbary(Expert Mainframe Engineer)
    部门:Research:
            John Michaelis(Inventor)
    部门:Marketing:
     
    和Join不同,SQL中没有与GroupJoin等价的东西,这是由于SQL返回的数据是基于记录的,
    而不是分层次结构的。
     
    注:使用GroupJoin()与SelectMany() 外部连接
     
    9、调用SelectMany()
    处理由集合构造的集合
    SelectMany会遍布由Lambda表达式标识的每一项,并将每一项都放到一个新集合中。
    新集合整合了子集合中的所有项,所以,不像Select()那样返回两个集合,而是
    将选择每个数组都整合起来,把其中的项整合到一个集合中。
     
    Select是把要遍历的集合IEnumerable逐一遍历,每次返回一个T,
    合并之后直接返回一个IEnumerable,
    而SelectMany则把原有的集合IEnumerable每个元素遍历一遍,每次返回一个IEnumerable,
    把这些IEnumerable的“T”合并之后整体返回一个IEnumerable。
     
    因此我们可以说一般情况下SelectMany用于返回一个IEnumerable>的“嵌套”返回情况(把每个IEnumerable合并后返回一个整体的IEnumerable)。因此在嵌套的时候往往可以节省代码,
     
     1             string[] text = { "a b", "c d", "e f" };
     2             var items = text.Select(
     3                 item => item.Split(' ')
     4                 );
     5             foreach (string[] strs in items)
     6             {
     7                 foreach (string str in strs)
     8                 {
     9                     Console.WriteLine("	"+str);
    10                 }
    11             }
    12 换成SelectMany
    13             string[] text = { "a b", "c d", "e f" };
    14             var items = text.SelectMany(
    15                 item => item.Split(' ')
    16                 );
    17             foreach (var item in items)
    18             {
    19                 Console.WriteLine("	" + item);
    20             }
    输出:
            a
            b
            c
            d
            e
            f
     
    10、更多标准查询运算符
    由Enumerable提供的更简单的API都不需要Lambda表达式。
     
    System.Linq.Enumerable提供了一系列聚合函数,它们能枚举集合并计算一个结果。
    如Count()。
    ofType<T>()
    Union()
    Concat()
    Intersect()
    Distinct()
    SequenceEquals()
    Reverse()
     
    提供的聚合函数
    Count()
    Average()
    Sum()
    Max()
    Min()
     
     
    高级主题:IQuerable<T>的Queryable扩展
     
    有个接口几乎和IEnumerable<T>完全一样,这就是IQueryable<T>。
    由于IQueryable<T>是从IEnumerable<T>派生的,所以它有IEnumerable<T>的所有成员---
    但只有那些直接声明的,例如GetEnumerator()。扩展方法是不会继承的。
    所以没有Enumerable的任何扩展方法。
    然后,它有一个类似的扩展类,称为System.Linq.Queryable。
    IQuerable<T>使自定义的LINQ Provider成为功能。
    LINQ Provider的作用是将表达式分解成各个组成部分。
    一经分解,表达式就可以转换成另一种语言,可以序列化以便在远程执行,
    可以通过一个异步执行模式来注入。
    简单地说,LINQ Provider在一个标准集合API中引入了一个“解释”机制。
    利用这种几乎没有任何限制的功能,与查询和集合有关的行为就可以“注入”。
     
    如利用LINQ Provider,可以将查询表达式从C#转换成SQL,然后在一个远程数据库上执行。
    这样一来,C#程序就可以继续使用他熟悉的面向对象语言,将向SQL的转换留给底层的LINQ
    Provider 去完成。
     
    谨慎:推迟执行,以及不知不觉中反复调用任何代价高昂、
    可能推迟执行的操作。
  • 相关阅读:
    编译原理知识点整理
    LeetCode 3.无重复字符的最长字串
    LeetCode 2.两数相加
    LeetCode 1.两数之和
    《硅谷之火》中的个人计算机梦
    Linux常用命令行指令(持续更新~)
    idea常用快捷键(随时更新~)
    解决idea中使用maven创建spring mvc项目时创建过慢问题
    spring实战第二章小记-装配bean
    HTML5 Video播放服务端大文件
  • 原文地址:https://www.cnblogs.com/tlxxm/p/4674988.html
Copyright © 2020-2023  润新知