C#的linq语句

 /查询表达式必须以form子句开头,以select或者group子句结束,在这两个子句之间,可以使用
        ///where 、orderby、join、let和其他的from子句

本章要点 
    用列表在对象上执行传统查询 
    扩展方法 
    LINQ查询操作符 
    平行LNQ 
    表达式树 
    本章源代码下载地址( wrox. com )
    打开网页www.wrox.com/go/procsharp,单击DownloadCode选项卡即可下载本章源代码。本章 
    弋码分为以下几个主要的示例文件 
    ·LINQ Intro 
    ·Enumerable Sample 
    ·rallel LINQ 
    · Expression Trees 
11.1LNQ概述 
    语言集成查询( Language Integrated Query,LNQ在C#编程语言中集成了查询语法,可以用相同 
的语法访问不同的数据源。LNQ提供了不同数据源的抽象层,所以可以使用相同的语法。 
本章介绍LINQ的核心原理和C#中支持C#LNQ查询的语言扩展。 
读完本章后,在数据库中使用LNQ的内容可查阅第33章,查询MML数据的内 
容可参见第34章 
在介绍LNQ的特性之前,本节先介绍一个简单的LNQ查询。C#提供了转换为方法调用的集成查
询语言。本节会说明这个转换的过程,以便用户使用LNQ的全部功能。 

111.1列表和实体 
    本章的LNQ查询在一个包含1950-2011年级方程式锦标赛的集合上进行,这些数据需要使 
用实体类和列表来准备。 
对于实体,定义类型 Racer. Racer定义了几个属性和个重载的 ToString0方法,该方法以字符 
串格式显示赛车手。这个类实现了 IFormattabl接口,以支持格式字符串的不同变体,这个类还实 
现了 IComparable<Racer接口,它根据 Lastname为组赛车手排序为了执行更高级的查询, Racer 
类不仅包含单值属性,如 Firstname、 Lastname、Wns、 Country和Sats,还包含多值属性,如Cans 
和 Years. Years属性列出了赛车手获得冠军的年份。一些赛车手曾多次获得冠军。Cars属性用于列 
出赛车手在获得冠军的年份中使用的所有车型代码文件 MataLab/ Racers)
 ........

11.1.2LINQ查询     

        /// <summary>
        /// 使用这些准备好的列表和实体,进行LINQ查询,例如查询出来自巴西的所有世界冠军,
        /// 并按照夺冠次数排序,为此可以使用List<T>类的方法,如FindAll()和Sort()方法,
        /// 而使用LINQ的语法非常简单如下:
        /// 
        ///下面就是一个LINQ查询,子句form、where、orderby、dexcending 和select都是这个查询中预定义的
        ///关键子。
        ///查询表达式必须以form子句开头,以select或者group子句结束,在这两个子句之间,可以使用
        ///where 、orderby、join、let和其他的from子句
        ///变量query只是指定了LINQ查询,该查询不是通过这个赋值语句执行的,当使用foreach循环访问的时候
        ///该查询就会执行
        /// </summary>
        static void LINQQuery()
        {
            var query = from r in Formula1.GetChampions()
                        where r.Country == "Brazil"
                        orderby r.Wins descending
                        select r;
            foreach (var r in query)
            {
                Console.WriteLine("{0:A}", r);
            }
        }
  

11.1.3 扩展方法

 
    编译器会转换LNQ查询,以调用方法而不是LNQ查询。LNQ为 IEnumerable<T>接口提供 
了各种扩展方法,以便用户在实现了该接口的任意集合上使用LNQ查询。扩展方法在静态类中声 
明,定义为一个静态方法,其中第一个参数定义了它扩展的类型 
扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个特定接口 
的任何类中,这样多个类就可以使用相同的实现代码 
    例如, String类没有Foo()方法, String类是密封的,所以不能从这个类中继承。但可以创建 
个扩展方法,如下所示 
public static class stringExtension 
{
   public static void Foo(this string s)
   {
    Console. WriteLine("Foo invoked for (01",s);
  }
}

    扩展方法定义为静态方法,其第一个参数定义了它扩展的类型,扩展方法在一个静态类中声明 
Foo方法扩展了 String类,因为它的第一个参数定义为 String类型,为了区分扩展方法和般的静 
态方法,扩展方法还需要对第一个参数使用this关键字 
    现在就可以使用带 string类型的Foo方法了: 
    string a="Hel1";
          a.Foo() ;
    结果在控制台上显示“ Foo invoked for Hello”,因为 Hello是传递给FoO方法的字符串。 
    也许这看起来违反了面向对象的规则,因为给一个类型定义了新方法,但没有改变该类型或派生 
自它的类型。但实际上并非如此。扩展方法不能访问它扩展的类型的私有成员。调用扩展方法只是调 
用静态方法的种新语法。对于字符串,可以用如下方式调用Foo()方法,获得相同的结果 
    string s=“Hel1o” ;
    stringExtension Foo (s);
要调用静态方法,应在类名的后面加上方法名。扩展方法是调用静态方法的另一种方式。不必 
提供定义了静态方法的类名,相反,编译器调用静态方法是因为它带的参数类型。只需导入包含该
类的名称空间,就可以将Foo()扩展方法放在 String类的作用域中 
    定义LNQ扩展方法的个类是 System. Linq名称空间中的 Numerable.只需要导入这个名称 
空间,就打开了这个类的扩展方法的作用域。下面列出了Where()扩展方法的实现代码。 where()扩 
展方法的第一个参数包含了this关键字,其类型是 IEnumerable<T>。这样, where()方法就可以用于 
实现了正 IEnumerable<T>的每个类型例如数组和List<T>类实现了 IEnumerable<T>接口。第二个参 
数是一个Fun<T,bool>委托,它引用了一个返回布尔值、参数类型为T的方法,这个谓词在实现代 
码中调用,检查 IEnumerable<T>源中的项是否应放在目标集合中,如果委托引用了该方法, yield 
return语句就将源中的项返回给目标。 
public static IEnumerable<TSource>where<TSource>( 
          this IEnumerable<tsource> source, 
            Func<TSource, bool> predicate) 
{
       foreach (TSource item in source) 
        if (predicate (item)) 
}

    因为Whee()作为一个泛型方法实现,所以它可以用于包含在集合中的任意类型。实现了 
IEnumerabl<T>接口的任意集合都支持它 
 
    现在就可以使用Enumerable类中的扩展方法Where()、OrderByDescending()和Select()。这些方 
法都返回正 IEnumerable< TSource>,所以可以使用前面的结果依次调用这些方法。通过扩展方法的参 
数,使用定义了委托参数的实现代码的匿名方法
      static void ExtensionMethods()
        {
            var champions = new List<Racer>(Formula1.GetChampions());
            IEnumerable<Racer> brazilChampions =
                champions.Where(r => r.Country == "Brazil").
                        OrderByDescending(r => r.Wins).
                        Select(r => r);
            foreach (Racer r in brazilChampions)
            {
                Console.WriteLine("{0:A}", r);
            }
        }

11.14推迟查询的执行 
    在运行期间定义查询表达式时,查询就不会运行。查询会在迭代数据项时运行 
    再看看扩展方法 Wbere()它使用 yield retum语句返回谓词为ue的元素因为使用了 yield returm 
语句,所以编译器会创建一个枚举器,在访问枚举中的项后,就返回它们 

  public static IEnunerable<T> where<T>(this IEnumerable<T> source, func<T, bool> predicate)
        {
            foreach (T item in source)
            {
                if (predicate(item))
                {
                    yield return item;
                }
            }
        }

    这是一个非常有趣也非常重要的结果。在下面的例子中,创建了Sting元素的一个集合,用名 
称填充它。接着定义一个查询,从集合中找出以字母J开头的所有名称。集合也应是排好序的。在 
定义查询时,不会进行迭代。相反,迭代在 foreach语句中进行,在其中迭代所有的项。集合中只有 
个元素Juan满足 where表达式的要求,即以字母J开头。迭代完成后,将Juan写入控制台。之后 
在集合中添加4个新名称,再次进行迭


            var names = new List<string> { " Nino", "Alberto", "Juan", "Mike", "Phil" };
            var nameswithJ = from n in names
                             where n.StartsWith("J")
                             orderby n
                             select n;
            Console.WriteLine("First iteration");
            foreach (string name in nameswithJ)
            {
                Console.WriteLine(name);
            }

            Console.WriteLine();
            names.Add("John");
            names.Add("Jim");
            names.Add("Jack");
            names.Add("Denny");
            Console.WriteLine("Second iteration");
            foreach (string name in nameswithJ) {
                Console.WriteLine(name);
            }  

     因为选代在查询定义时不会进行,而是在执行每个 foreach语句时进行,所以可以看到其中的变 
化,如应用程序的结果所示 
    First iteration 
    Juan 

    Second iteration 
    Jack
    Jim
    John 
    Juan 
    当然,还必须注意,每次在选代中使用查询时,都会调用扩展方法。在大多数情况下,这是非 
常有效的,因为我们可以检测出源数据中的变化。但是在一些情况下,这是不可行的。调用扩展
法 ToArmay(), TList()等可以改变这个操作在示例中, ToList遍历集合返回一个实现了 List<stnng>
的集合。之后对返回的列表遍历两次,在两次迭代之间,数据源得到了新名称


            var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" };
            var nameswithJ = (from n in names
                              where n.StartsWith("J")
                              orderby n
                              select n).ToList();
            Console.WriteLine("First teration");
            foreach (string name in nameswithJ)
            {
                Console.WriteLine(name);
            }
            Console.WriteLine();
            names.Add("Jim");
            names.Add("Jack");
            names.Add("Denny ");
            Console.WriteLine("Second iteration");
            foreach (string name in nameswithJ)
            {
                Console.WriteLine(name);
            }
    在结果中可以看到,在两次选代之间输出保持不变,但集合中的值改变了 
        First iteration 
        Juan
        Second iteration 
        Juan
         

 

11.21筛选 
下面介绍一些查询的示例 
使用where子句,可以合并多个表达式,例如,找出赢得至少15场比赛的巴西和奕地利赛车 
传递给 where子句的表达式的结果类型应是布尔类型:
   
          var racers = from r in Formula1.GetChampions()
                         where r.Wins > 15 &&
                             (r.Country == "Brazil" || r.Country == "Austria")
                         select r;
            foreach (var r in racers) {
                Console.WriteLine("(0: A)", r);
            }
    用这个LNQ查询启动程序,会返回 Nia Lauda、 Nelson Piquet和 Ayrton Senna,如下
    Niki Lauda, Austria, Starts: 173, Wins: 25 
    Nelson Piquet, Brazil, Starts: 204, wins: 23 
    Ayrton Senna, Brazil, starts: 16l, Wins: 41 

    并不是所有的查询都可以用LNQ查询语法完成。也不是所有的扩展方法都映射到LNQ查询 
子句上。高级查询需要使用扩展方法。为了更好地理解带扩展方法的复杂查询,最好看看简单的查 
询是如何映射的。使用扩展方法 Where()和 Select(),会生成与前面LNQ查询非常类似的结果:
 
        var racers = Formula1.GetChampions().
            Where(x => x.Wins > 15 && (x.Country == "Bra211" || x.Country == "Austria")).
            Select(r => r);


11.2.2用索引筛选 
不能使用LNQ查询的一个例子是Wbere()方法的重载,在Where()方法的重载中,可以传递第 
二个参数----索引。索引是筛选器返回的每个结果的计数器可以在表达式中使用这个索引,执行 
基于索引的计算。下面的代码由 where扩展方法调用,它使用索引返回姓氏以A开头、索引为偶 
数的赛车手(代码文件 EnumerableSample/Program.cs)

 static void IndexFiltering()
        {
            var racers = Formula1.GetChampions().
                    Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0);
            foreach (var r in racers)
            {
                Console.WriteLine("{0:A}", r);
            }
        }
姓氏以A开头的所有赛车手有 Alberto ascan、 Manio Andretti和 Femando alonso。因为 Mano 
Andretti的索引是奇数,所以他不在结果中 
Alberto Ascari, Italy: starts: 32, wins: 10 
Fernando Alonso, Spain: starts: 177, wins: 27 

11.23类型筛选 
    为了进行基于类型的筛选,可以使用 flype扩展方法,这里数组数据包含sng和it对象 
使用 OfTypeO扩展方法,把 string类传送给泛型参数,就从集合中仅返回字符串(代码文件 
numerableSample/Program.cs) 

  static void TypeFiltering()
        {
            object[] data = { "one", 2, 3, "four", "five", 6 };
            var query = data.OfType<string>();
            foreach (var s in query)
            {
                Console.WriteLine(s);
            }
        }
运行这段代码,就会显示字符串one、four和fve 
one
four
five 

11.24复合的from子句 

    如果需要根据对象的一个成员进行筛选,而该成员本身是一个系列,就可以使用复合的fom子 
句。Racer类定义了一个属性Cars,其中Cars是一个字符串数组。要筛选驾驶法拉利的所有冠军, 
可以使用如下所示的LNQ查询第一个from子句访问从 Formula1.GetChampions()方法返回的 Racer 
对象,第二个fom子句访问 Racer类的Cars属性,以返回所有 stnng类型的赛车。接着在whee子 
句中使用这些赛车筛选驾驶法拉利的所有冠军(代码文件 Enumerable Sample/Program.cs 

  static void CompoundFrom()
        {
            var ferrariDrivers = from r in Formula1.GetChampions()
                                 from c in r.Cars
                                 where c == "Ferrari"
                                 orderby r.LastName
                                 select r.FirstName + " " + r.LastName;
            foreach (var racer in ferrariDrivers)
            {
                Console.WriteLine(racer);
            }
        }
    这个查询的结果显示了驾驶法拉利的所有一级方程式冠军: 
    Alberto Ascari 
    Juan Manuel Fangio
    Mike Hawthorn 
    hil HiiI 
    Niki Lauda 
    R土 koren 
    Scheckter 
    I Schumacher 
    John surtees 

    C#编译器把复合的form子句和LNQ查询转换为 SelectMary()扩展方法。 SelectMary()方法可 
用于迭代序列的序列。示例中 SelectMary()方法的重载版本如下所示 

  public static IEnunerable<TResult> SelectMany<TSource, TCollection, TResult>( 
            this IEnumerable<TSource> source,
            Func<TSource,IEnumerable<TCollection>> collectionSelectcr,
            Func<TSource, TCollection, TResult> resulSelector);


    第一个参数是隐式参数,它从GetChampions()方法中接收 Racer对象序列。第二个参数是 
collectionSelectcr委托,其中定义了内部序列。在 lambda表达式r=>r.Cars中,应返回赛车集合。 
第三个参数是个委托,现在为每个赛车调用该委托,接收Racer和Car对象。 lambda表达式创建 
了一个匿名类型,它有 Racer和Car属性,这个 SelectMary()方法的结果是摊平了赛车手和赛车的层 
次结构,为每辆赛车返回匿名类型的一个新对象集合 
    这个新集合传递给 Where方法,筛选出驾驶法拉利的赛车手。最后,调用 OrderBy()和Select() 
方法 

         var ferrariDrivers = Formula1.GetChampions().
                SelectMany(r => r.Cars, (r, c) => 
                new { Racer = r, Car = c }).
                Where(r => r.Car == "Ferrari").
                OrderBy(r => r.Racer.LastName).
                Select(r => r.Racer.FirstName + " " + r.Racer.LastName);

把 SelectMany()泛型方法解析为这里使用的类型,所解析的类型如下所示。在这个例子中,数 
据源是Raer类型,所筛选的集合是一个sing数组,当然所返回的匿名类型的名称是未知的,这 
里显示为 TResult 

    public static IEnunerable<TResult> SelectMany<Racer, string, TResult>(
            this IEnumerable<Racer> source,
            Func<Racer, IEnumerable<string>> collectionSelectcr,
            Func<Racer, string, TResult> resulSelector);

查询仅从LNQ查询转换为扩展方法,所以结果与前面 

11.25排序 
    要对序列排序,前面使用了 orderby子句。下面复习一下前面使用的例子,但这里使用 orderby 
descending子句。其中赛车手按照豪得比赛的次数进行降序排序,赢得比赛的次数用关键字选择器 
指定(代码文件 EnumerableSample/Program.cs) 

static void Filtering()
        {
            var racers = from r in Formula1.GetChampions()
                         where r.Wins > 15 && (r.Country == "Brazil" || r.Country == "Austria")
                         select r;
            foreach (var r in racers)
            {
                Console.WriteLine("{0:A}", r);
            }
        }

    orderby子句解析为 OrderByo方法, orderby descending子句解析为 OrderBy Descending方法: 
     var racers = from r in Formula1.GetChampions().
                         Where(r => r.Country == "Brazil").
                         OrderByDescending(r => r.Wins).
                         Select(r=>r);

     OrderBy()和 OrderByDescending()方法返回 IOrderEnumerable<TSource>,这个接口派生自 
IEnumerable<TSource>接口,但包含一个额外的方法 CreateOrderedEnumerable<TSource>()。这个方 
法用于进一步给序列排序。如果根据关键字选择器来排序,其中有两项相同,就可以使用 ThenBy()
和 ThenByDescending()方法继续排序。这两个方法需要 IOrderEnumerable<TSource>接口才能工作, 
但也返回这个接口。所以,可以添加任意多个 Then By0和 Then By Descending方法,对集合排序 
    使用LNQ查询时,只需要把所有用于排序的不同关键字(用逗号分隔开漆加到 orderby子句中 
在下例中,所有的赛车手先按照国家排序,再按照姓氏排序,最后按照名字排序。添加到LNQ查 
询结果中的Take扩展方法用于提取前10个结果 
var racers. (from r in Formula1.Getchampions() 
    orderby r.Country, r.LastName, r.FirstName 
    select r). Take (10); 

    排序后的结果如下 
    Argentina: Fangio, Juan Man 
    Australia: Brabham, Jack 
    Australia: Jones, Alan 
    Austr⊥a: Lauda,Nk1 
    Austria: Rindt, Jochen 
    Brazil: Fittipaldi, Emerson 
    Brazil: Piquet, Nelson 
    Brazil: Senna, Ayrto 
    Canada: Villeneuve, Jacques 
    F⊥ nland: Hakkinen,M⊥ka 

    使用 Order ByO和 Then Byo扩展方法可以执行相同的操作 

    var racers- Formulal Getchampions ( 
    OrderBy(r r Country) 
    ThenBy(r -> r LastName) 
    ThenBy (r - r FirstName) 
    Take (10): 


11.26分组 
    要根据一个关键字值对查询结果分组,可以使用group子句,现在一级方程式冠军应按照国家 
分组,并列出一个国家的冠军数。子句 group r by r.Country into g根据 Country属性组合所有的赛车 
手,并定义一个新的标识符g,它以后用于访问分组的结果信息。 group子句的结果根据应用到分组 
结果上的扩展方法Count()来排序,如果冠军数相同,就根据关键字来排序,该关键字是国家,因为 
这是分组所使用的关键字。 where子句根据至少有两项的分组来筛选结果,select子句创建一个带
Country 和 Count 属性的匿名类型
    static void Grouping()
        {
            var countries = from r in Formula1.GetChampions()
                            group r by r.Country into g
                            orderby g.Count() descending, g.Key
                            where g.Count() >= 2
                            select new
                            {
                                Country = g.Key,
                                Count = g.Count()
                            };

            foreach (var item in countries)
            {
                Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
            }

        }

 结果显示了带Country和Count属性的对象集合





    要用扩展方法执行相同的操作,应把 groupby子句解析为 GroupBy()方法。在 GroupBy()方法的 
声明中,注意它返回实现了 IGRouping接口的枚举对象, IGRouping接囗定义了Key属性,所以在定 
义了对这个方法的调用后,可以访问分组的关键字: 

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey >( 
         this IErumerable<tSource> source, Func<Tsource, TKey> keySelector);

    把子句 group r by r.Country into g解析为 GroupBy(r=> r.Country),返回分组序列。分组序列首先 用 Order By Descending()方法排序,再用 ThenBy()方法排序。接着调用 Where()和Select()方法 

 var countries = Formula1.GetChampions().
                GroupBy(r => r.Country).
                OrderByDescending(g => g.Count()).
                ThenBy(g => g.Key).
                Where(g => g.Count() >= 2).
                Select(g => new
                    { Country = g.Key,  Count = g.Count()});

11.2.7对嵌套的对象分组 

    如果分组的对象应包含嵌套的序列,就可以改变 select子句创建的匿名类型,在下面的例子中 
所返回的国家不仅应包含国家名和赛车手数量这两个属性,还应包含赛车手名序列。这个序列用 
个赋予 Racers属性的 from/in内部子句指定,内部的form子句使用分组标识符g获得该分组中的所 
有赛车手用姓氏对它们排序再根据姓名创建一个新字符串(代码文件 Enumerablesample/programes) 

     static void GroupingWithNestedObjects()
        {
            var countries = from r in Formula1.GetChampions()
                            group r by r.Country into g
                            orderby g.Count() descending, g.Key
                            where g.Count() >= 2
                            select new
                            {
                                Country = g.Key,
                                Count = g.Count(),
                                Racers = from r1 in g
                                         orderby r1.LastName
                                         select r1.FirstName + " " + r1.LastName
                            };
            foreach (var item in countries)
            {
                Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
                foreach (var name in item.Racers)
                {
                    Console.Write("{0}; ", name);
                }
                Console.WriteLine();
            }
        }


 11.28内连接

 11.2.8内连接
     使用join子句可以根据特定的条件合并两个数据源,但之前要获得两个要连接的列表。在级 
方程式比赛中,有赛车手冠军和车队冠军。赛车手从 GetChampions()方法中返回,车队从 GetConstru- 
ctorChampions()方法中返回。现在要获得一个年份列表,列出每年的赛车手冠军和车队冠军 
为此,先定义两个查询,用于查询赛车手和车队(代码文件 EnumerableSample/Program.cs)

 static void InnerJoin()
        {
            var racers = from r in Formula1.GetChampions()
                         from y in r.Years
                         select new
                         {
                             Year = y,
                             Name = r.FirstName + " " + r.LastName
                         };

            var teams = from t in Formula1.GetContructorChampions()
                        from y in t.Years
                        select new
                        {
                            Year = y,
                            Name = t.Name
                        };

        //有了这两个查洵,再通过join子句,根据赛车手获得冠军的年份和车队获得冠军的年份进行连 
        //接。 select子句定义了一个新的匿名类型,它包含Year、 Racer和Team属性
            var racersAndTeams =
                  (from r in racers
                   join t in teams on r.Year equals t.Year
                   orderby t.Year
                   select new
                   {
                       Year = r.Year,
                       Champion = r.Name,
                       Constructor = t.Name
                   }).Take(10);

            Console.WriteLine("Year  World Champion\t   Constructor Title");
            foreach (var item in racersAndTeams)
            {
                Console.WriteLine("{0}: {1,-20} {2}",
                   item.Year, item.Champion, item.Constructor);
            }

           
        }

      

 11.2.9  左外连接

 11.2.9左外连接 
    上一个连接示例的输出从1958年开始,因为从这一年开始,才同时有了赛车手冠军和车队冠军 
赛车手冠军出现得更早一些,是在1950年。使用内连接时,只有找到了匹配的记录才返回结果。为 
了在结果中包含所有的年份,可以使用左外连接。左外连接返回左边序列中的全部元素,即使它们 
在右边的序列中并没有匹配的元素。 
    下面修改前面的LNQ查询,使用左外连接。左外连接用jon子句和 DefaultlfEmpty方法定义 
如果查询的左侧赛车手没有匹配的车队冠军那么就使用 DefaultlfEmpty方法定义其右侧的默认值 
(代码文件 Enumerable Sample/Program.cs 
static void LeftOuterJoin()
        {
            var racers = from r in Formula1.GetChampions()
                         from y in r.Years
                         select new
                         {
                             Year = y,
                             Name = r.FirstName + " " + r.LastName
                         };

            var teams = from t in Formula1.GetContructorChampions()
                        from y in t.Years
                        select new
                        {
                            Year = y,
                            Name = t.Name
                        };

            var racersAndTeams =
              (from r in racers
               join t in teams on r.Year equals t.Year into rt
               from t in rt.DefaultIfEmpty()
               orderby r.Year
               select new
               {
                   Year = r.Year,
                   Champion = r.Name,
                   Constructor = t == null ? "no constructor championship" : t.Name
               }).Take(10);

            Console.WriteLine("Year  Champion\t\t   Constructor Title");
            foreach (var item in racersAndTeams)
            {
                Console.WriteLine("{0}: {1,-20} {2}",
                   item.Year, item.Champion, item.Constructor);
            }
        }

11.2.10组链接

11.2.10组连接 
    左外连接使用了组连接和into子句。它有一部分语法与组连接相同,只不过组连接不使用 
第1部分群#语言 
DefaultlfEmpty方法 
    使用组连接时,可以连接两个独立的序列,对于其中一个序列中的某个元素,另一个序列中存 
在对应的一个项列表 
    下面的示例使用了两个独立的序列。一个是前面例子中已经看过的冠军列表。另一个是一个 
Championship类型的集合。下面的代码段显示了 Championship类,该类包含冠军年份以及该年份中 
获得第一名、第二名和第三名的赛车手,对应的属性分别为Year, First、 Second和 Third代码文


  public class Championship
  {
    public int Year { get; set; }
    public string First { get; set; }
    public string Second { get; set; }
    public string Third { get; set; }
  }
Championship 方法返回了冠军集合,如下面的代码段所示
 private static List<Championship> championships;
    public static IEnumerable<Championship> GetChampionships()
    {
      if (championships == null)
      {
        championships = new List<Championship>();
        championships.Add(new Championship
        {
          Year = 1950,
          First = "Nino Farina",
          Second = "Juan Manuel Fangio",
          Third = "Luigi Fagioli"
        });
        championships.Add(new Championship
        {
          Year = 1951,
          First = "Juan Manuel Fangio",
          Second = "Alberto Ascari",
          Third = "Froilan Gonzalez"
        });
        ......      }
      return championships;
    }

冠军列表应与每个冠军年份中获得前三名的赛车手构成的列表组合起来,然后显示每一年的结果
   RacerInfo 类定义了要显示的信息,如下所示:
  public class RacerInfo
  {
    public int Year { get; set; }
    public int Position { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }

    使用连接语句可以把两个列表中的赛车手组合起来 
    为冠军列表中的每一项都包含3个赛车手,所以首先需要把这个列表摊平。一种方法是使用 
Selectmany方法。该方法使用的 lambda表达式为冠军列表中的每项返回包含3项的一个列表。在 
这个 lambda表达式的实现中,因为 RacerInfo包含 FirstName和 LastName属性,而收到的集合只包 
含带有 First, Second和 Third属性的一个名称,所以必须拆分字符串,这可以通过扩展方法 FirstName 
和 SecondName完成(代码文件 Enumerable Sample/ Programes


      var racers = Formula1.GetChampionships().SelectMany(cs => new List<RacerInfo>()
       {
         new RacerInfo {
           Year = cs.Year,
           Position = 1,
           FirstName = cs.First.FirstName(),
           LastName = cs.First.LastName()
         },
         new RacerInfo {
           Year = cs.Year,
           Position = 2,
           FirstName = cs.Second.FirstName(),
           LastName = cs.Second.LastName()
         },
         new RacerInfo {
           Year = cs.Year,
           Position = 3,
           FirstName = cs.Third.FirstName(),
           LastName = cs.Third.LastName()
         }
       });

  扩展方法FirstName 和SecondName使用空格字符拆分字符串

    public static class StringExtension
    {
        public static string FirstName(this string name)
        {
            int ix = name.LastIndexOf(' ');
            return name.Substring(0, ix);
        }
        public static string LastName(this string name)
        {
            int ix = name.LastIndexOf(' ');
            return name.Substring(ix + 1);
        }
    }
    现在就可以连接两个序列。Formula1.GetChampions()返回一个racers 列表, racers变量返回包含 
年份、比赛结果和赛车手姓名的一个 RacerInfo列表。仅使用姓氏比较两个集合中的项是不够的。有 
时候列表中可能同时包含了一个赛车手和他的父亲(如 Damon H和 Graham Hil,所以必须同时使 
用 FirstName和 LastName进行比较,这是通过为两个列表创建一个新的匿名类型实现的。通过使用 
ino子句,第二个集合中的结果被添加到了变量 yearResults中,对于第一个集合中的每个赛车手, 
第I部分c语言 都创建了一个 yearResults,它包含了在第二个集合中匹配名和姓的结果最后,用LNQ查询创建 
了一个包含所需信息的新匿名类型 

 var q = (from r in Formula1.GetChampions()
                     join r2 in racers on
                     new
                     {
                         FirstName = r.FirstName,
                         LastName = r.LastName
                     }
                     equals
                     new
                     {
                         FirstName = r2.FirstName,
                         LastName = r2.LastName
                     }
                     into yearResults
                     select new
                     {
                         FirstName = r.FirstName,
                         LastName = r.LastName,
                         Wins = r.Wins,
                         Starts = r.Starts,
                         Results = yearResults
                     });

            foreach (var r in q)
            {
                Console.WriteLine("{0} {1}", r.FirstName, r.LastName);
                foreach (var results in r.Results)
                {
                    Console.WriteLine("{0} {1}", results.Year, results.Position);
                }
            }

11.1.11集合操作

参考C#高级编程第九版本

猜你喜欢

转载自blog.csdn.net/penghao_1/article/details/86496364