65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 课程 - 第10课:LINQ 简介,LINQ to Objects 第一部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (19投票s)

2016年7月19日

CPOL

18分钟阅读

viewsIcon

28219

downloadIcon

563

我的系列文章的第10篇。我们将讨论 LINQ 的总体概念以及 LINQ to Objects 的延迟执行操作符。

全部课程集

引言

现代世界围绕数据构建。数据就是一切。与几十年前相比,世界上每个人每天接收的信息量都大得多。大部分信息来自某个应用程序。无论你是在浏览器上阅读新闻、听广播还是看电视,它们很可能最初是在 Word 应用程序中以文本形式创建并保存到文件或数据库中的。数据、数据、数据,一切都与数据有关,其中 99% 由过去创建、正在创建或将来创建的软件处理。我想说的是,现代软件的目的主要是处理数据。这些数据可能以不同的格式进入你的应用程序,在本文中,我将专注于文本数据处理。我所说的文本数据是指以符号集形式保存、传输和处理的数据。这不一定是我们能读懂的文本,也可以是数字、日期和时间值等。

文本数据可以从不同的来源和不同的格式到达你的应用程序。你可以通过邮件应用程序、通过网络协议从另一个应用程序接收数据,或者从文件或数据库读取。传输和处理文本数据的方式有很多非标准的方法,也有一些存储和传输文本数据的标准。例如,数据通常以 JSON 或 XML 的形式传输,并保存在关系型或非关系型数据库中。在应用程序运行时,程序员有很多读取、写入,尤其是搜索数据的需求。为了更轻松地组织数据处理工作,已经创建了成千上万的私有和公共库及类。微软开发团队也决定在这个领域做出贡献,并创建一个内建于语言中的抽象机制,用于查询数组、集合、数据库、XML 文档等数据。他们创建了 LINQ,我将在本文中介绍它。

LINQ 是什么?

让我们从名称开始。LINQMicrosoft's Language Integrated Query(微软语言集成查询)的缩写。顾名思义,LINQ 与查询有关。每个查询都返回匹配条件的对象的集合或特定对象。在 LINQ 中,这个返回的数据集合称为序列。LINQ 中的几乎所有序列的类型都是 IEnumerable<T>。你的 C# 应用程序无需安装任何东西即可使用 LINQ。LINQ 自 3.5 版本和 Visual Studio 2008 起已集成到 .NET 中。

总的来说,LINQ 可以分为以下几个方面:

  1. LINQ to Objects - 允许对内存中的数据集合和数组执行查询。
  2. LINQ to XML - 用于处理 XML 的 API 部分的名称。
  1. LINQ to DataSet - 用于处理 DataSets 的 API。
  1. LINQ to SQL - API 的一部分,允许你向 Microsoft SQL 数据库执行查询。
  1. LINQ to Entities - 这是使用 Entity Framework 与数据库交互的另一种方式。

IEnumerable<T>、序列、标准查询操作符和 yield

LINQ 的一个很棒之处在于它与 C# 完全集成,并且可以与你在使用 LINQ 之前就已经使用的相同容器和数组一起使用。LINQ 的功能是通过 IEnumerable<T> 接口实现的。这个接口被所有 C# 的泛型集合和数组实现。通过这个接口,可以实现集合的枚举。

当你对集合执行某个方法并得到 IEnumerable<T> 结果时,这称为序列。序列是 LINQ 中的另一个重要术语。如果你有一个声明为 IEnumerable<T> 的变量,它就被称为T 的序列。例如,IEnumerable<string> 是字符串的序列。

除了 IEnumerable 和序列之外,LINQ 中还有第三件重要的事情,那就是标准查询操作符。其中大多数定义在 System.Linq.Enumerable 中,并设计为以 IEnumerable<T> 作为第一个参数。由于查询操作符是扩展方法,因此最好在 IEnumerable<T> 类型的变量上调用它们,而不是将该变量作为第一个参数传递。当你组合不同的操作符时,你可能会得到非常复杂的查询。大多数标准查询操作符返回 IEnumerable<T> 查询。标准查询操作符有几十个。你可以在 MSDN 上通过 此链接查看所有这些操作符。我们将在本文及后续文章中回顾它们的使用。

Yielding 和延迟查询

 重要的是要知道,操作符仅在结果查询被枚举时执行。这称为 yielding。由于查询本身仅在枚举特定元素时执行,因此它可能包含在编译时无法捕获的错误。下面的示例演示了这个问题。

            string[] CarBrands = { "Mersedes", "Ford", "Lexus", "Toyota",
                                   "Honda", "Hyunday", "BMW", "KIA", "Chevrolet",
                                   "Tesla", "Lamborghini", "Ferrari", "Lincoln",
                                    "Cadillac"};
            //----------------yielding----------------------
            Console.WriteLine("-----------------YIELDING--------------------");
            IEnumerable<string> cars = CarBrands.Where(s => s.StartsWith("T"));
            //result for below is Toyota and Tesla
            foreach (string car in cars) 
            {
                Console.WriteLine(car);
            }
            //the query below is problematic and has exception out of range for values of "BMW" and "KIA"
            try
            {
                cars = CarBrands.Where(s => s[3]!='D');
                foreach (string car in cars)
                {
                    Console.WriteLine(car);
                }
            }
            catch (IndexOutOfRangeException ex)
            {
                Console.WriteLine("Out of range catched");
            }

如你所见,即使查询已编译,它也可能包含错误。你应该对此非常小心,并确保在处理不同数据集时测试你的代码并处理查询可能发生的任何潜在错误。

由于查询仅在引用特定对象时执行的另一个潜在陷阱是,如果同一个查询被执行两次,而你在中间更改了原始数据集,那么这些查询的结果将是不同的。下面的示例演示了查询是延迟执行的事实。

            //----------------deferred query----------------------
            Console.WriteLine("-----------------DEFERRED QUERY--------------------");
            int[] arr = new int[] { 1, 2, 3, 4, 5 };
            IEnumerable<int> ints = arr.Select(i => i);
            foreach (int i in ints)
            {
                Console.WriteLine(i);//result is 1,2,3,4,5
            }
            arr[0] = 10;
            foreach (int i in ints)
            {
                Console.WriteLine(i);//result is 10,2,3,4,5
            }

 

Func 委托

某些标准查询操作符接受 Func 委托作为参数。下面是 Func 委托的声明。

public delegate TR Func<TR>();
public delegate TR Func<T0, TR>(T0 a0);
public delegate TR Func<T0, T1, TR>(T0 a0, T1 a1);
public delegate TR Func<T0, T1, T2, TR>(T0 a0, T1 a1, T2 a2);
public delegate TR Func<T0, T1, T2, T3, TR>(T0 a0, T1 a1, T2 a2, T3 a3);

在这些声明中,TR 是返回值的类型。其他 T0、T1 等是函数的参数。当你调用任何需要某种 Func 委托作为输入参数的操作符时,通常称为谓词。在这个谓词中,你通常会有一些输入类型,最后一个参数应该是返回类型。

例如,让我们回顾一下 IEnumerable 的 Count 操作符,它在 MSDN 上的定义如下:

Count<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)

返回一个数字,表示指定序列中有多少个元素满足某个条件。

这是 C# 声明:

public static int Count<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> predicate
)

如你所见,第二个参数是一个委托,它接收源作为输入并返回一个布尔值,指示输入源值是否满足某个条件。你应该熟悉 Func 委托的语法,以理解需要你做什么。

LINQ to Objects 延迟执行操作符

下面我将回顾目前存在的 LINQ to Objects 的延迟执行操作符。几乎所有的示例都将使用以下数据结构进行执行:

            string[] CarBrands = { "Mersedes", "Ford", "Lexus", "Toyota",
                                   "Honda", "Hyunday", "BMW", "KIA", "Chevrolet",
                                   "Tesla", "Lamborghini", "Ferrari", "Lincoln",
                                    "Cadillac"};

有关更多详细信息,请下载本文附带的源代码并执行它以查看结果。

 

  • Where - 此操作符用于将输入数据结构的元素筛选到输出序列中。Where 有两个重载:
    • public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T, bool> predicate); - 此版本接受输入源和谓词方法委托,并返回谓词方法返回 true 的对象的序列。如你所见,谓词方法应该接受类型 T 并返回 bool。Where 接收输入序列的每个元素并将其传递给谓词方法。
    • public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - 此版本与第一个版本类似,但此外它还接收一个整数值作为谓词函数的输入。这个整数值是输入序列中元素的索引号。你应该记住索引是从零开始的,所以第一个元素的索引将是 0,最后一个元素的索引等于序列中元素数量减一。

代码

            Console.WriteLine("-----------------WHERE");
            //first version
            Console.WriteLine("--first version");
            IEnumerable<string> outCars = CarBrands.Where(s => s.StartsWith("C"));//gives Chevrole and Cadillac
            foreach (string car in outCars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            outCars = CarBrands.Where((s, i) => { return (i & 1) == 0; });//gives cars whose index is odd
            foreach (string car in outCars)
            {
                Console.WriteLine(car);
            }

结果

  • Select - 此操作符用于从一种类型的输入序列元素创建另一种类型的输出序列元素。输入序列和输出序列的元素可以是相同类型的,也可以不是。此函数有两个重载:
    •  public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source,Func<T, S> selector); - 此版本接受输入序列和选择器方法委托。
    • public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source,Func<T, int, S> selector); - Select 的第二个版本,除了下一个元素外,还接收该元素的输入索引号。与 Where 操作符中的索引相同。

代码

            Console.WriteLine("--first version");
            //here we transofrm input sequence of strings to output sequence of integers that present their lenghts
            IEnumerable<int> lengths = CarBrands.Select( s => s.Length);
            foreach (int i in lengths)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("--second version");
            var CarsAndTheirIndexes = CarBrands.Select((s, i) => new { CarName = s, CarIndex = i});
            foreach (var Car in CarsAndTheirIndexes)
            {
                Console.WriteLine("Car name is: " + Car.CarName + " Car index is: " + Car.CarIndex);
            }

结果

 

  • SelectMany - 此操作符用于创建一对多投影……此函数也有两个原型:
    • public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source,Func<T, IEnumerable<S>> selector); - 此版本接受输入序列作为源和选择器委托。此委托以输入序列的每个元素作为输入。它应该返回一个对象,当你枚举它时,它将向输出序列产生零个或多个 S 类型的元素。SelectMany 操作符的结果将是从所有这些 S 类型序列中连接起来的输出。
    • public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source,Func<T, int, IEnumerable<S>> selector); - 第二个版本与第一个版本完全相同,只是将元素的索引作为输入添加到选择器方法中。

代码

            Console.WriteLine("--first version");
            //here we return list of characters for a car that starts with C and othervise return empty array
            var CarsLetters = CarBrands.SelectMany(car => { if(car.StartsWith("C"))return car.ToArray(); return new char[0];});
            foreach (char c in CarsLetters) 
            {
                Console.Write(c);
            }
            Console.WriteLine("--second version");
            CarsLetters = CarBrands.SelectMany((car,index) => { if((index&1) == 0)return car.ToArray(); return new char[0]; });
            foreach (char c in CarsLetters)
            {
                Console.Write(c);
            }

结果

  • Take - 从序列的开头返回指定数量的元素。此操作符只有一个原型:
    • public static IEnumerable<T> Take<T>(this IEnumerable<T> source,int count); - 该函数根据输入的整数返回一个序列,该整数指示输出序列中包含输入序列的多少个元素。注意,如果你输入的元素数量大于输入序列中的元素数量,你不会收到任何错误,并且你的输出序列将包含输入序列中的所有元素。

代码

            Console.WriteLine("\n-----------------TAKE");
            IEnumerable<string> ThreeCars = CarBrands.Take(3);
            foreach (string car in ThreeCars)
            {
                Console.WriteLine(car);
            }

结果

  • TakeWhile - 此操作符从输入序列中获取元素并将其放入输出序列,直到某个条件为真。此操作符有两个原型:
    • public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source,Func<T, bool> predicate); - 此函数接受输入序列和谓词函数,该函数将接收输入序列的每个元素并应返回 bool。一旦返回 false,该操作符就会停止从输入序列产生元素并结束其工作。
    • public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - 与第一个版本相同,但增加了输入序列中元素的索引。

代码

            Console.WriteLine("\n-----------------TAKEWHILE");
            Console.WriteLine("--first version");
            cars = CarBrands.TakeWhile(s => s.Length > 3);//will return all cars until we reach BMW
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            cars = CarBrands.TakeWhile((s,i) => i < 5);//will return first 5 cars
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • Skip - 此操作符与 Take 操作符相反。它会跳过输入序列中的指定数量的元素,并输出其余所有元素直到末尾。它有一个原型:
    • public static IEnumerable<T> Skip<T>(this IEnumerable<T> source,int count); - 此函数返回一个基于输入序列的序列,但不包含开头由 count 值定义的元素数量。
CODE:

            Console.WriteLine("\n-----------------SKIP");
            IEnumerable<string> CarsWithoutFirstThree = CarBrands.Skip(3);
            foreach (string car in CarsWithoutFirstThree)
            {
                Console.WriteLine(car);
            }

结果

  • SkipWhile - 此操作符会在某个条件为真时跳过输入序列的元素。一旦该条件变为 false,它就会将所有剩余元素返回到输出序列。有两个版本:
    • public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source,Func<T, bool> predicate); - 此函数接受输入序列并将其所有元素传递给谓词函数。在谓词返回 true 时,这些元素将被跳过。一旦它为某个元素返回 false,你就会在输出序列中收到输入序列中的所有剩余元素。
    • public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - 与上一个相同,但谓词还接收输入元素的索引。

代码

            Console.WriteLine("\n-----------------SKIPWHILE");
            Console.WriteLine("--first version");
            cars = CarBrands.SkipWhile(s => !s.StartsWith("C"));//will return all cars after Chevrole including it also
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            cars = CarBrands.SkipWhile((s, i) => i < 5);//will return cars without first 4
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • Concat - 此操作符连接两个输入序列。它具有以下原型:
    • public static IEnumerable<T> Concat<T>(this IEnumerable<T> first,IEnumerable<T> second); - 这里输入的是两个相同类型的句子,结果将是它们的组合。

代码

            Console.WriteLine("\n-----------------CONCAT");
            string[] FewMoreCars = { "Acura", "Infinity" };
            cars = CarBrands.Concat(FewMoreCars);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • OrderBy - 使用此操作符,你可以根据返回每个元素键值的键选择器方法对输入序列进行排序。此操作符返回排序后的序列 IOrderedEnumerable<T> 作为结果,它将基于键的值进行生成。此操作符具有以下原型:
    • public static IOrderedEnumerable<T> OrderBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector) where K : IComparable<K>; - 此函数接受输入序列源和委托函数 keySelector,该函数接收源的每个元素并基于该元素返回一个键。然后,此方法根据此键对输入序列中的所有元素进行排序。keySelector 返回的键的类型必须实现 IComparable 接口。
    • public static IOrderedEnumerable<T> OrderBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - 与第一个版本类似,但在这里你将 IComparer 对象传递给此函数,它将执行两个 K 实例的比较。在这种情况下,keySelector 返回的值不一定需要实现 IComparable。

代码

            Console.WriteLine("\n-----------------ORDERBY");
            Console.WriteLine("--first version");
            cars = CarBrands.OrderBy(s => s.Length);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            MyStringComparer comparer = new MyStringComparer();
            cars = CarBrands.OrderBy((s => s), comparer);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • OrderByDescending - 与 OrderBy 相同,但按降序排序。也有两个原型:
    • public static IOrderedEnumerable<T> OrderByDescending<T, K>(this IEnumerable<T> source,Func<T, K> keySelector) where K : IComparable<K>; - 此函数接受输入序列源和委托函数 keySelector,该函数接收源的每个元素并基于该元素返回一个键。然后,此方法根据此键对输入序列中的所有元素进行降序排序。keySelector 返回的键的类型必须实现 IComparable 接口。
    • public static IOrderedEnumerable<T> OrderByDescending<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - 与第一个版本类似,但在这里你将 IComparer 对象传递给此函数,它将执行两个 K 实例的比较。在这种情况下,keySelector 返回的值不一定需要实现 IComparable。

代码

            Console.WriteLine("\n-----------------ORDERBYDESCENDING");
            Console.WriteLine("--first version");
            cars = CarBrands.OrderByDescending(s => s.Length);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            cars = CarBrands.OrderByDescending((s => s), comparer);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • ThenBy - 此操作符接受已排序的序列(作为 IOrderedEnumerable)并根据返回键值的函数再次对其进行排序。有两个原型:
    • public static IOrderedEnumerable<T> ThenBy<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector)where K : IComparable<K>; - 此函数接受 IOrderedEnumerable 序列作为输入,并返回相同的数据结构作为输出。keySelector 委托接收输入序列的每个元素并基于输入元素返回一个键。返回的键必须实现 IComparable。
    • public static IOrderedEnumerable<T> ThenBy<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - 与第一个版本相同,但返回的键实现 IComparer。

代码

            Console.WriteLine("\n-----------------THENBY");
            Console.WriteLine("--first version");
            cars = CarBrands.OrderBy(s => s.Length).ThenBy(s => s);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            comparer = new MyStringComparer();
            cars = CarBrands.OrderBy(s => s.Length).ThenBy(s=>s, comparer);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

关于 ThenBy 操作符的一个重要说明是,它保证如果方法依次接收的两个元素的输入序列具有相同的键值,它们在输出序列中的顺序将完全相同。

 

  • ThenByDescending - 与上一个操作符完全相同,但按降序排序。
    • public static IOrderedEnumerable<T> ThenByDescending<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector)where K : IComparable<K>; - 此函数接受 IOrderedEnumerable 序列作为输入,并返回相同的数据结构作为输出。keySelector 委托接收输入序列的每个元素并基于输入元素返回一个键。返回的键必须实现 IComparable。
    • public static IOrderedEnumerable<T> ThenByDescending<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - 与第一个版本相同,但返回的键实现 IComparer。

代码

            Console.WriteLine("\n-----------------THENBYDESCENDING");
            Console.WriteLine("--first version");
            cars = CarBrands.OrderBy(s => s.Length).ThenByDescending(s => s);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            comparer = new MyStringComparer();
            cars = CarBrands.OrderBy(s => s.Length).ThenByDescending(s => s, comparer);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

与 ThenBy 类似,ThenByDescending 操作符保证如果方法依次接收的两个元素的输入序列具有相同的键值,它们在输出序列中的顺序将完全相同。

 

  • Reverse - 以相反的顺序返回与输入序列相同的序列。有一个原型:
    • public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source); - 没有复杂的输入参数 source,它表示输入序列。

代码

            Console.WriteLine("\n-----------------REVERSE");
            cars = CarBrands.Reverse();
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

结果

  • Join - 此操作符返回两个序列中键匹配的元素的组合。具有以下原型:
    • public static IEnumerable<V> Join<T, U, K, V>(this IEnumerable<T> outer,IEnumerable<U> inner,Func<T, K> outerKeySelector,Func<U, K> innerKeySelector, Func<T, U, V> resultSelector); - 这里第一个参数 outer 是我们调用 Join 操作符的序列,因为这个函数是扩展方法。第二个参数 inner 是另一个我们作为输入传递给此方法的序列。首先,为 inner 序列的每个元素调用 innerKeySelector,它将为每个元素返回一个键,并且 inner 序列的每个元素将基于其键存储在哈希表中。接下来,枚举 outer 序列,并为它的每个元素调用 outerKeySelector 来获取其键。如果我们要在 inner 元素的哈希表中找到一个键匹配的元素,那么就会调用 resultSelector。resultSelector 方法将返回 V 类型的实例化对象。

代码

            Console.WriteLine("\n-----------------JOIN");
            int[] Lenghts = { 3, 4, 5, 6, 7, 8, 9 };
            var outStructs = CarBrands.Join(Lenghts, s => s.Length, l => l, (s, l) => new { name = s, length = l });
            foreach (var Car in outStructs)
            {
                Console.WriteLine("Car name is: " + Car.name + " Car name length is: " + Car.length);
            }

结果

  • GroupJoin - 对两个输入序列执行分组连接。与 Join 操作符类似,但它将所有匹配的内部元素分组到 outer 序列的一个元素中。该函数具有以下原型:
    • public static IEnumerable<V> GroupJoin<T, U, K, V>(this IEnumerable<T> outer,IEnumerable<U> inner,Func<T, K> outerKeySelector,Func<U, K> innerKeySelector, Func<T, IEnumerable<U>, V> resultSelector); - 这里 outer 参数是我们调用该函数的序列,inner 参数是输入序列。首先,此操作符将调用 innerKeySelector 方法来枚举输入序列的所有 U 类型元素,并将该序列的所有元素存储在由键 K 引用的哈希表中。下一步将为每个 T 类型元素调用 outerKeySelector 以检索其键 K。当检索到键 K 时,它会获取哈希表中所有与键 K 匹配的 inner 序列元素。最后一步将是调用 resultSelector 方法,并将 outer 元素和 inner 元素序列(其键与 outer 元素键匹配)传递给它。

代码

            Console.WriteLine("\n-----------------GROUPJOIN");
            //if car length is bigger than 5 we take all lengths that bigger than 5
            //if car length is smaller or equal 5 we take all lengths that smaller or equal 5
            int[] Len = { 3, 4, 5, 6, 7, 8, 9 };
            var outValues = CarBrands.GroupJoin(Len, s => s.Length > 5, l => l > 5, (s, lens) => new { name = s, lensplus = lens });
            foreach (var car in outValues)
            {
                Console.WriteLine("Car name is: " + car.name + "name length: "+ car.name.Length +" Lengths bigger or smaller and equal than 5: ");
                foreach (int l in car.lensplus) 
                {
                    Console.WriteLine(l);
                }
            }

结果

  • GroupBy - 此操作符按公共键对输入序列的元素进行分组。具有以下原型:
    • public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector); - 此函数枚举输入源序列并为每个元素调用 keySelector 方法。然后,使用该元素及其键,该函数会生成 IGrouping<K,T> 序列,其中该组的每个元素是具有相同键的元素序列。键值的比较使用默认的相等比较器。
    • public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IEqualityComparer<K> comparer);  - 此原型与第一个完全相同,但你可以将自己的相等比较器放在默认比较器位置。
    • public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source,Func<T, K> keySelector,Func<T, E> elementSelector); - 与第一个原型相同,但你可以指定要通过 elementSelector 遍历的元素,而不是整个序列。
    • public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source,Func<T, K> keySelector,Func<T, E> elementSelector,IEqualityComparer<K> comparer); - 第四个原型是第二个和第三个的组合,在这里你可以决定要分组的元素,并为键提供自己的比较器。

代码

            Console.WriteLine("\n-----------------GROUPBY");
            Console.WriteLine("--first version");
            IEnumerable<IGrouping<int, string>> grouppedCars = CarBrands.GroupBy(s => s.Length);
            foreach(IGrouping<int, string> element in grouppedCars)
            {
                Console.WriteLine("Length: " + element.Key + " Names:");
                foreach(string s in element)
                {
                    Console.WriteLine(s);
                }
            }
            Console.WriteLine("--second version");
            IntComparer comp = new IntComparer();
            grouppedCars = CarBrands.GroupBy(s => s.Length,comp);
            foreach (IGrouping<int, string> element in grouppedCars)
            {
                Console.WriteLine("Length: " + element.Key + " Names:");
                foreach (string s in element)
                {
                    Console.WriteLine(s);
                }
            }
            Console.WriteLine("--third version");
            IEnumerable<IGrouping<int, int>> grouppedLengths = CarBrands.GroupBy(s => s.Length, el => el.Length);
            foreach (IGrouping<int, int> element in grouppedLengths)
            {
                Console.WriteLine("Length group: " + element.Key + " Cars lengths:");
                foreach (int s in element)
                {
                    Console.WriteLine(s);
                }
            }
            Console.WriteLine("--fourth version");
            grouppedLengths = CarBrands.GroupBy(s => s.Length, el => el.Length, comp);
            foreach (IGrouping<int, int> element in grouppedLengths)
            {
                Console.WriteLine("Length group: " + element.Key + " Cars lengths:");
                foreach (int s in element)
                {
                    Console.WriteLine(s);
                }
            }

结果

  • Distinct - 此操作符从输入源中移除所有重复项,并返回一个不包含任何重复项的新序列。具有以下原型:
    • public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source); - 如你所见,这是一个非常简单的函数,我们对其调用源序列,它返回一个相同类型的序列。

代码

            Console.WriteLine("\n-----------------DISTINCT");
            string[] names = { "Sergey", "Sergey", "John", "James", "John", "Mary", "Alexander" };
            IEnumerable<string> dist = names.Distinct();
            foreach (string s in dist)
            {
                Console.WriteLine(s);
            }

结果

  • Union - 此操作符返回两个输入序列的唯一值集合,具有以下原型:
    • public static IEnumerable<T> Union<T>(this IEnumerable<T> first,IEnumerable<T> second); - 此函数首先枚举名为 first 的输入序列,并获取尚未获取的元素,然后它以完全相同的方式枚举名为 second 的序列。

代码

            Console.WriteLine("\n-----------------UNION");
            string[] names2 = { "Mary", "Alexander", "James", "Richard", "Julia" };
            IEnumerable<string> uniqueNames = names.Union(names2);
            foreach (string s in uniqueNames)
            {
                Console.WriteLine(s);
            }

结果

  • Intersect - 返回两个输入序列的交集。具有以下原型:
    • public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first,IEnumerable<T> second); - 此函数首先枚举名为 second 的输入序列的元素,只取唯一的元素,然后枚举 first 序列中的元素,取那些在 second 中被枚举过的元素,并将它们放入输出序列。

代码

            Console.WriteLine("\n-----------------INTERSECT");
            IEnumerable<string> intersNames = names.Intersect(names2);
            foreach (string s in intersNames)
            {
                Console.WriteLine(s);
            }

结果

  • Except - 此操作符返回 first 序列中不存在于 second 序列中的所有元素。存在以下函数原型:
    • public static IEnumerable<T> Except<T>(this IEnumerable<T> first,IEnumerable<T> second); - 非常简单的函数。我们在 first 序列上调用它,并将 second 作为输入。

代码

            Console.WriteLine("\n-----------------EXCEPT");
            IEnumerable<string> exceptNames = names.Except(names2);
            foreach (string s in exceptNames)
            {
                Console.WriteLine(s);
            }

结果

  • Cast - 此操作符将输入序列的每个元素转换为特定类型的输出序列。有一个原型:
    • public static IEnumerable<T> Cast<T>(this IEnumerable source); - 如你所见,此操作符在类型为 IEnumerable 的序列 source 上调用。如你从前面的所有操作符中看到的,大多数操作符接受 IEnumearble<T> 而不是 IEnumearble,所以请不要感到困惑,这确实是不同的。注意:cast 将尝试将输入序列的每个对象转换为输出序列类型。如果某个对象不可转换,你将收到 InvalidCastException。

代码

            Console.WriteLine("\n-----------------CAST");
            int[] integers = { 1, 2, 3, 4, 5, 6, 7 };
            IEnumerable<object> objs = integers.Cast<object>();
            foreach (object s in objs)
            {
                Console.WriteLine(s.ToString());
            }

结果

  • OfType - 此操作符与 Cast 类似,但它只返回可以转换为输出序列类型的元素。它具有以下原型:
    • public static IEnumerable<T> OfType<T>(this IEnumerable source); - 与 Сast 相同,它接受 IEnumerable 接口。

代码

            Console.WriteLine("\n-----------------OFTYPE");
            IEnumerable<string> strs = integers.OfType<string>();//here we'll receive empty string
            foreach (string s in strs)
            {
                Console.WriteLine(s.ToString());
            }

结果

  • DefaultIfEmpty - 如果输入序列为空,此函数将返回包含默认值的序列。具有以下原型:
    • public static IEnumerable<T> DefaultIfEmpty<T>(this IEnumerable<T> source); - 此函数在输入序列 source 上调用。如果输入序列为空,它将返回输入序列的默认值 <T>,对于引用类型和可空类型,它为 null。
    • public static IEnumerable<T> DefaultIfEmpty<T>(this IEnumerable<T> source,T defaultValue); - 与第一个相同,但允许设置默认值。

代码

            Console.WriteLine("\n-----------------DEFAULTIFEMPTY");
            Console.WriteLine("--first version");
            IEnumerable<int> sequence = integers.Where(i => i>10);
            IEnumerable<int> outseq = sequence.DefaultIfEmpty();//here we should have default int value
            Console.WriteLine("Result on empty:");
            foreach(int i in outseq)
            {
                Console.WriteLine(i);
            }
            sequence = integers.Where(i => i > 5);
            outseq = sequence.DefaultIfEmpty();//here we should have default int value
            Console.WriteLine("Result on not empty:");
            foreach (int i in outseq)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("--second version");
            sequence = integers.Where(i => i > 10);
            outseq = sequence.DefaultIfEmpty(55);//here we should have default int value
            Console.WriteLine("Result on empty:");
            foreach (int i in outseq)
            {
                Console.WriteLine(i);
            }

结果

  • Range - 生成整数序列,具有以下原型:
    • public static IEnumerable<int> Range(int start,int count);

代码

            Console.WriteLine("\n-----------------RANGE");
            IEnumerable<int> intseq = Enumerable.Range(5, 5);
            foreach (int i in intseq)
            {
                Console.WriteLine(i);
            }

结果

  • Repeat - 此操作符生成一个输出序列,该序列包含特定输入元素指定的次数。有一个原型:
    • public static IEnumerable<T> Repeat<T>(T element,int count);

代码

            Console.WriteLine("\n-----------------REPEAT");
            intseq = Enumerable.Repeat(5, 5);
            foreach (int i in intseq)
            {
                Console.WriteLine(i);
            }

结果

  • Empty - 生成特定类型的空序列。有一个原型:
    • public static IEnumerable<T> Empty<T>();

代码

            Console.WriteLine("\n-----------------EMPTY");
            intseq = Enumerable.Empty<int>();
            foreach (int i in intseq)
            {
                Console.WriteLine(i);//we should never reach here
            }

结果

来源

  1. Pro LINQ Language Integrated Query in C# 2010 Adam Freeman and Joseph C. Rattz, Jr.
  2. https://msdn.microsoft.com
  3. https://codeproject.org.cn/Articles
© . All rights reserved.