技术开发 频道

LINQ项目-更多标准查询操作符


【IT168技术文档】

  除了上述基本查询工具之外,许多操作符也提供了操作序列和编写查询的有用方法,从而在标准查询操作符的方便架构中为用户提供对结果的高级控制。

  排序和分组

  一般而言,对查询表达式的计算会导致以某种顺序生成一系列值,该顺序是底层信息源的固有顺序。要使开发人员能够显式控制这些值的生成顺序,应定义标准查询操作符来控制该顺序。这些操作符中最基本的就是 OrderBy 操作符。

  OrderBy 和 OrderByDescending 操作符可应用于任何信息源,并允许用户提供可生成用于排序结果的值的键值提取函数。  OrderBy 和 OrderByDescending 还接受可用于对键施加部分顺序的可选比较函数。下面我们来看一个基本示例:
string[] names = { "Burke", "Connor", "Frank", "Everett", "Albert", "George", "Harris", "David" }; // unity sort var s1 = names.OrderBy(s => s); var s2 = names.OrderByDescending(s => s); // sort by length var s3 = names.OrderBy(s => s.Length); var s4 = names.OrderByDescending(s => s.Length);
  前两个查询表达式会生成根据字符串比较排序源成员的新序列。后两个查询会生成根据每个字符串的长度排序源成员的新序列。

  要允许多个排序准则,OrderBy 和 OrderByDescending 都应该返回 SortedSequence,而不是通用的 IEnumerable。两个操作符仅在 SortedSequence 上定义,分别名为 ThenBy 和 ThenByDescending,它们将应用附加(从属)的排序准则。  ThenBy/ThenByDescending 自身会返回 SortedSequence,以允许应用任何数量的 ThenBy/ThenByDescending 操作符:
string[] names = { "Burke", "Connor", "Frank", "Everett", "Albert", "George", "Harris", "David" }; var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);
  在本例中,计算由 s1 引用的查询将生成以下值序列:
"Burke", "David", "Frank", "Albert", "Connor", "George", "Harris", "Everett"
  除了 OrderBy 系列的操作符,标准查询操作符还包括 Reverse 操作符。Reverse 只枚举序列并以相反的顺序生成相同的值。与 OrderBy 不同,Reverse 在决定顺序时不考虑实际值本身,而仅仅依赖于底层源生成的值的顺序。

  OrderBy 操作符可对值序列施加排序顺序。标准查询操作符还包括 GroupBy 操作符,该操作符可根据键值提取函数对值序列进行分区。GroupBy 操作符会返回一列 Grouping 值,其中每一个对应于所遇到的不同的键值。每个 Grouping 都包含键,以及映射到该键的值集合。Grouping 的公共协定如下所示:
public sealed class Grouping { public Grouping(K key, IEnumerable group); public Grouping(); public K Key { get; set; } public IEnumerable Group { set; get; } }
  最简单的 GroupBy 应用程序如下所示:
string[] names = { "Albert", "Burke", "Connor", "David", "Everett", "Frank", "George", "Harris"}; // group by length var grouping = names.GroupBy(s => s.Length); foreach (Grouping group in grouping) { Console.WriteLine("Strings of length {0}", group.Key); foreach (string value in group.Group) Console.WriteLine(" {0}", value); }
  运行后,该程序会显示出以下结果:
Strings of length 6 Albert Connor George Harris Strings of length 5 Burke David Frank Strings of length 7 Everett

  Select 和 GroupBy 允许您提供用于填充组成员的投影函数。
string[] names = { "Albert", "Burke", "Connor", "David", "Everett", "Frank", "George", "Harris"}; // group by length var grouping = names.GroupBy(s => s.Length, s => s[0]); foreach (Grouping group in grouping) { Console.WriteLine("Strings of length {0}", group.Key); foreach (char value in group.Group) Console.WriteLine(" {0}", value); }
  该变体会显示以下结果:
Strings of length 6 A C G H Strings of length 5 B D F Strings of length 7 E
  从该示例中可以注意到,投影类型不需要与源相同。在本例中,我们从字符串序列创建了整数-字符的分组。

  聚合操作符

  定义几个标准查询操作符,以便将一列值聚合到单个值中。最常见的聚合操作符是 Fold,它定义如下:
public static U Fold(this IEnumerable source, U seed, Func func) { U result = seed; foreach (T element in source) result = func(result, element); return result; }
  Fold 操作符使得针对值序列进行计算很简单。Fold 的工作方法是,为底层序列的每个成员调用一次 λ 表达式。每次 Fold 调用 λ 表达式时,都会传递序列的成员和一个聚合值(初始值基于 Fold 的种子参数)。λ 表达式的结果会替换先前的聚合值,Fold   将返回 λ 表达式的最终结果。

  例如,以下程序使用 Fold 针对一个字符串数组计算总字符数:
string[] names = { "Albert", "Burke", "Connor", "David", "Everett", "Frank", "George", "Harris"}; int count = names.Fold(0, (c, s) => c + s.Length); // count == 46
  除了通用的 Fold 操作符,标准查询操作符还包含一个通用的 Count 操作符和四个数值聚合操作符(Min、Max、Sum 和 Average),以便简化这些常见的聚合操作。只要提供将序列成员投影到数值类型的函数,数值聚合函数就可以处理数值类型的序列(例如,int、 double、decimal)或任意值序列。

  以下程序演示 Sum 操作符的上述两种形式:
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; string[] names = { "Albert", "Burke", "Connor", "David", "Everett", "Frank", "George", "Harris"}; int total1 = numbers.Sum(); // total1 == 55 int total2 = names.Sum(s => s.Length); // total2 == 46
  请注意,第二个 Sum 语句等效于前面使用 Fold 的示例。

  Select 与 SelectMany

  Select 操作符要求转换函数为源序列中的每个值生成一个值。如果转换函数返回的值本身就是一个序列,则应该由使用者手动遍历子序列。例如,请考虑以下程序,该程序使用现有的 String.Split 方法将字符串拆分为标记:
string[] text = { "Albert was here", "Burke slept late", "Connor is happy" }; var tokens = text.Select(s => s.Split(' ')); foreach (string[] line in tokens) foreach (string token in line) Console.Write("{0}.", token);

  运行后,该程序会显示以下文本:
Albert.was.here.Burke.slept.late.Connor.is.happy.
  理想情况下,我们应该让查询返回标记的合并序列,并且不对使用者公开中间 string[]。为此,我们将使用 SelectMany 操作符,而不是 Select 操作符。SelectMany 操作符的工作方式类似于 Select 操作符。但不同之处在于,转换函数返回的序列随后由 SelectMany 操作符扩展。下面是使用 SelectMany 重新编写的程序:
string[] text = { "Albert was here", "Burke slept late", "Connor is happy" }; var tokens = text.SelectMany(s => s.Split(' ')); foreach (string token in tokens) Console.Write("{0}.", token);
  使用 SelectMany 会导致每个中间序列扩展为正常计算的一部分。
0
相关文章