本篇主要介绍标准查询运算符的常用运算功能。
排序操作基于一个或多个属性对序列的元素进行排序。 第一个排序条件对元素执行主要排序。 通过指定第二个排序条件,您可以对每个主要排序组内的元素进行排序。
下图展示了对一系列字符执行按字母顺序排序操作的结果。
下节列出了对数据进行排序的标准查询运算符方法。
方法
OrderBy | 按升序对值排序。 | orderby | Enumerable.OrderBy |
OrderByDescending | 按降序对值排序。 | orderby … descending | Enumerable.OrderByDescending |
ThenBy | 按升序执行次要排序。 | orderby …, … | Enumerable.ThenBy |
ThenByDescending | 按降序执行次要排序。 | orderby …, … descending | Enumerable.ThenByDescending |
Reverse | 反转集合中元素的顺序。 | 不适用。 | Enumerable.Reverse |
查询表达式语法示例
主要排序示例
主要升序排序
下面的示例演示如何在 LINQ 查询中使用 orderby
子句按字符串长度对数组中的字符串进行升序排序。
string[] words = { "the", "quick", "brown", "fox", "jumps" }; IEnumerable<string> query = from word in words
orderby word.Length
select word; foreach (string str in query)
Console.WriteLine(str); /* 输出:
the
fox
quick
brown
jumps
*/
主要降序排序
下面的示例演示如何在 LINQ 查询中使用 orderby descending
子句按字符串的第一个字母对字符串进行降序排序。
string[] words = { "the", "quick", "brown", "fox", "jumps" }; IEnumerable<string> query = from word in words
orderby word.Substring(, ) descending
select word; foreach (string str in query)
Console.WriteLine(str); /* 输出:
the
quick
jumps
fox
brown
*/
次要排序示例
次要升序排序
下面的示例演示如何在 LINQ 查询中使用 orderby
子句对数组中的字符串执行主要和次要排序。 首先按字符串长度,其次按字符串的第一个字母,对字符串进行升序排序。
string[] words = { "the", "quick", "brown", "fox", "jumps" }; IEnumerable<string> query = from word in words
orderby word.Length, word.Substring(, )
select word; foreach (string str in query)
Console.WriteLine(str); /* 输出:
fox
the
brown
jumps
quick
*/
次要降序排序
下面的示例演示如何在 LINQ 查询中使用 orderby descending
子句按升序执行主要排序,按降序执行次要排序。 首先按字符串长度,其次按字符串的第一个字母,对字符串进行排序。
string[] words = { "the", "quick", "brown", "fox", "jumps" }; IEnumerable<string> query = from word in words
orderby word.Length, word.Substring(, ) descending
select word; foreach (string str in query)
Console.WriteLine(str); /* 输出:
the
fox
quick
jumps
brown
*/
LINQ 中的集运算是指根据相同或不同集合(或集)中是否存在等效元素来生成结果集的查询运算。
下节列出了执行集运算的标准查询运算符方法。
方法
Distinct | 删除集合中的重复值。 | 不适用。 | Enumerable.Distinct |
Except | 返回差集,差集指位于一个集合但不位于另一个集合的元素。 | 不适用。 | Enumerable.Except |
相交 | 返回交集,交集指同时出现在两个集合中的元素。 | 不适用。 | Enumerable.Intersect |
联合 | 返回并集,并集指位于两个集合中任一集合的唯一的元素。 | 不适用。 | Enumerable.Union |
比较集运算
Distinct
下图演示字符序列上 Enumerable.Distinct 方法的行为。 返回的序列包含输入序列的唯一元素。
Except
下图演示 Enumerable.Except 的行为。 返回的序列只包含位于第一个输入序列但不位于第二个输入序列的元素。
相交
下图演示 Enumerable.Intersect 的行为。 返回的序列包含两个输入序列共有的元素。
联合
下图演示对两个字符序列执行的联合操作。 返回的序列包含两个输入序列的唯一元素。
筛选是指将结果集限制为仅包含满足指定条件的元素的操作。 它也称为选定内容。
下图演示了对字符序列进行筛选的结果。 筛选操作的谓词指定字符必须为“A”。
下面一节列出了执行所选内容的标准查询运算符方法。
方法
OfType | 根据其转换为特定类型的能力选择值。 | 不适用。 | Enumerable.OfType |
Where | 选择基于谓词函数的值。 | where | Enumerable.Where |
查询表达式语法示例
以下示例使用 where
子句从数组中筛选具有特定长度的字符串。
string[] words = { "the", "quick", "brown", "fox", "jumps" }; IEnumerable<string> query = from word in words
where word.Length ==
select word; foreach (string str in query)
Console.WriteLine(str); /* 输出:
the
fox
*/
限定符运算返回一个 Boolean 值,该值指示序列中是否有一些元素满足条件或是否所有元素都满足条件。
下图描述了两个不同源序列上的两个不同限定符运算。 第一个运算询问是否有一个或多个元素为字符“A”,结果为 true
。 第二个运算询问是否所有元素都为字符“A”,结果为 true
。
下节列出了执行限定符运算的标准查询运算符方法。
方法
全部 | 确定是否序列中的所有元素都满足条件。 | 不适用。 | Enumerable.All |
任意 | 确定序列中是否有元素满足条件。 | 不适用。 | Enumerable.Any |
包含 | 确定序列是否包含指定的元素。 | 不适用。 | Enumerable.Contains |
投影是指将对象转换为一种新形式的操作,该形式通常只包含那些将随后使用的属性。 通过使用投影,您可以构造从每个对象生成的新类型。 可以投影属性,并对该属性执行数学函数。 还可以在不更改原始对象的情况下投影该对象。
下面一节列出了执行投影的标准查询运算符方法。
方法
选择 | 投影基于转换函数的值。 | select | Enumerable.Select |
SelectMany | 投影基于转换函数的值序列,然后将它们展平为一个序列。 | 使用多个 from 子句 | Enumerable.SelectMany |
查询表达式语法示例
选择
下面的示例使用 select
子句来投影字符串列表中每个字符串的第一个字母。
List<string> words = new List<string>() { "an", "apple", "a", "day" }; var query = from word in words
select word.Substring(, ); foreach (string s in query)
Console.WriteLine(s); /* 输出:
a
a
a
d
*/
SelectMany
下面的示例使用多个 from
子句来投影字符串列表中每个字符串中的每个单词。
List<string> phrases = new List<string>() { "an apple a day", "the quick brown fox" }; var query = from phrase in phrases
from word in phrase.Split(' ')
select word; foreach (string s in query)
Console.WriteLine(s); /* 输出:
an
apple
a
day
the
quick
brown
fox
*/
Select 与 SelectMany
Select()
和 SelectMany()
的工作都是依据源值生成一个或多个结果值。 Select()
为每个源值生成一个结果值。 因此,总体结果是一个与源集合具有相同元素数目的集合。 与之相反,SelectMany()
生成单个总体结果,其中包含来自每个源值的串联子集合。 作为参数传递到 SelectMany()
的转换函数必须为每个源值返回一个可枚举值序列。 然后,SelectMany()
串联这些可枚举序列,以创建一个大的序列。
下面两个插图演示了这两个方法的操作之间的概念性区别。 在每种情况下,假定选择器(转换)函数从每个源值中选择一个由花卉数据组成的数组。
下图描述 Select()
如何返回一个与源集合具有相同元素数目的集合。
下图描述 SelectMany()
如何将中间数组序列串联为一个最终结果值,其中包含每个中间数组中的每个值。
代码示例
下面的示例比较 Select()
和 SelectMany()
的行为。 代码通过从源集合的每个花卉名称列表中提取前两项来创建一个“花束”。 此示例中,transform 函数 Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,TResult>) 使用的“单值”本身即是值的集合。 这需要额外的 foreach
循环,以便枚举每个子序列中的每个字符串。
class Bouquet
{
public List<string> Flowers { get; set; }
} static void SelectVsSelectMany()
{
List<Bouquet> bouquets = new List<Bouquet>() {
new Bouquet { Flowers = new List<string> { "sunflower", "daisy", "daffodil", "larkspur" }},
new Bouquet { Flowers = new List<string> { "tulip", "rose", "orchid" }},
new Bouquet { Flowers = new List<string> { "gladiolis", "lily", "snapdragon", "aster", "protea" }},
new Bouquet { Flowers = new List<string> { "larkspur", "lilac", "iris", "dahlia" }}
}; // *********** Select ***********
IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers); // ********* SelectMany *********
IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers); Console.WriteLine("Results by using Select():");
// 注意这里额外的foreach循环
foreach (IEnumerable<String> collection in query1)
foreach (string item in collection)
Console.WriteLine(item); Console.WriteLine("\nResults by using SelectMany():");
foreach (string item in query2)
Console.WriteLine(item); /* 输出: Results by using Select():
sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia Results by using SelectMany():
sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
*/ }
LINQ 中的分区是指将输入序列划分为两个部分的操作,无需重新排列元素,然后返回其中一个部分。
下图显示对字符序列进行三种不同的分区操作的结果。 第一个操作返回序列中的前三个元素。 第二个操作跳过前三个元素,返回剩余元素。 第三个操作跳过序列中的前两个元素,返回接下来的三个元素。
下面一节列出了对序列进行分区的标准查询运算符方法。
运算符
Skip | 跳过序列中指定位置之前的元素。 | 不适用。 | Enumerable.Skip |
SkipWhile | 基于谓词函数跳过元素,直到元素不符合条件。 | 不适用。 | Enumerable.SkipWhile |
Take | 获取序列中指定位置之前的元素。 | 不适用。 | Enumerable.Take |
TakeWhile | 基于谓词函数获取元素,直到元素不符合条件。 | 不适用。 | Enumerable.TakeWhile |
联接两个数据源就是将一个数据源中的对象与另一个数据源中具有相同公共属性的对象相关联。
当查询所面向的数据源相互之间具有无法直接领会的关系时,联接就成为一项重要的运算。在面向对象的编程中,这可能意味着在未建模对象之间进行关联,例如对单向关系进行反向推理。 下面是单向关系的一个示例:Customer 类有一个类型为 City 的属性,但 City 类没有作为 Customer 对象集合的属性。 如果你具有一个 City 对象列表,并且要查找每个城市中的所有客户,则可以使用联接运算完成此项查找。
LINQ 框架中提供的 join 方法包括 Join 和 GroupJoin。 这些方法执行同等联接,即根据 2 个数据源的键是否相等来匹配这 2 个数据源的联接。 (与此相较,Transact-SQL 支持除“等于”之外的联接运算符,例如“小于”运算符。)用关系数据库术语表达,就是说 Join 实现了内部联接,这种联接只返回那些在另一个数据集中具有匹配项的对象。 GroupJoin 方法在关系数据库术语中没有直接等效项,但实现了内部联接和左外部联接的超集。 左外部联接是指返回第一个(左侧)数据源的每个元素的联接,即使其他数据源中没有关联元素。
下图显示了一个概念性视图,其中包含两个集合以及这两个集合中的包含在内部联接或左外部联接中的元素。
方法
联接 | 根据键选择器函数联接两个序列并提取值对。 | join … in … on … equals … | Enumerable.Join |
GroupJoin | 根据键选择器函数联接两个序列,并对每个元素的结果匹配项进行分组。 | join … in … on … equals … into … | Enumerable.GroupJoin |
其他技术请参阅
分组是指将数据分到不同的组,使每组中的元素拥有公共的属性。
下图演示了对字符序列进行分组的结果。 每个组的键是字符。
下一节列出了对数据元素进行分组的标准查询运算符方法。
方法
GroupBy | 对共享通用属性的元素进行分组。 每组由一个 IGrouping<TKey,TElement> 对象表示。 | group … by 或
| Enumerable.GroupBy |
ToLookup | 将元素插入基于键选择器函数的 Lookup<TKey,TElement>(一种一对多字典)。 | 不适用。 | Enumerable.ToLookup |
查询表达式语法示例
下列代码示例根据奇偶性,使用 group by
子句对列表中的整数进行分组。
List<int> numbers = new List<int>() { , , , , , , , , , }; IEnumerable<IGrouping<int, int>> query = from number in numbers
group number by number % ; foreach (var group in query)
{
Console.WriteLine(group.Key == ? "\nEven numbers:" : "\nOdd numbers:");
foreach (int i in group)
Console.WriteLine(i);
} /* 输出:
Odd numbers:
35
3987
199
329 Even numbers:
44
200
84
4
446
208
*/
其他技术请参阅
生成是指创建新的值序列。
下面一节列出了执行生成的标准查询运算符方法。
方法
DefaultIfEmpty | 用默认值单一实例集合替换空集合。 | 不适用。 | Enumerable.DefaultIfEmpty |
空 | 返回一个空集合。 | 不适用。 | Enumerable.Empty |
范围 | 生成包含数字序列的集合。 | 不适用。 | Enumerable.Range |
Repeat | 生成包含一个重复值的集合。 | 不适用。 | Enumerable.Repeat |
元素运算从序列中返回唯一、特定的元素。
下节列出了执行元素运算的标准查询运算符方法。
方法
ElementAt | 返回集合中指定索引处的元素。 | 不适用。 | Enumerable.ElementAt |
ElementAtOrDefault | 返回集合中指定索引处的元素;如果索引超出范围,则返回默认值。 | 不适用。 | Enumerable.ElementAtOrDefault |
First | 返回集合的第一个元素或满足条件的第一个元素。 | 不适用。 | Enumerable.First |
FirstOrDefault | 返回集合的第一个元素或满足条件的第一个元素。 如果此类元素不存在,则返回默认值。 | 不适用。 | Enumerable.FirstOrDefault |
上一个 | 返回集合的最后一个元素或满足条件的最后一个元素。 | 不适用。 | Enumerable.Last |
LastOrDefault | 返回集合的最后一个元素或满足条件的最后一个元素。如果此类元素不存在,则返回默认值。 | 不适用。 | Enumerable.LastOrDefault |
Single | 返回集合的唯一一个元素或满足条件的唯一一个元素。如果没有要返回的元素或要返回多个元素,则引发 InvalidOperationException。 | 不适用。 | Enumerable.Single |
SingleOrDefault | 返回集合的唯一一个元素或满足条件的唯一一个元素。如果没有要返回的元素,则返回默认值。 如果要返回多个元素,则引发 InvalidOperationException。 | 不适用。 | Enumerable.SingleOrDefault |
转换方法可更改输入对象的类型。
LINQ 查询中的转换运算可用于各种应用程序。 以下是一些示例:
Enumerable.AsEnumerable 方法可用于隐藏类型的标准查询运算符自定义实现。
Enumerable.OfType 方法可用于为 LINQ 查询启用非参数化集合。
Enumerable.ToArray、Enumerable.ToDictionary、Enumerable.ToList 和 Enumerable.ToLookup方法可用于强制执行即时的查询,而不是将其推迟到枚举该查询时。
方法
下表列出了执行数据类型转换的标准查询运算符方法。
本表中名称以“As”开头的转换方法可更改源集合的静态类型,但不对其进行枚举。 名称以“To”开头的方法可枚举源集合,并将项放入相应的集合类型。
AsEnumerable | 返回类型化为 IEnumerable<T> 的输入。 | 不适用。 | Enumerable.AsEnumerable |
AsQueryable | 将(泛型)IEnumerable 转换为(泛型)IQueryable。 | 不适用。 | Queryable.AsQueryable |
Cast | 将集合中的元素转换为指定类型。 | 使用显式类型化的范围变量。 例如:
| Enumerable.Cast |
OfType | 根据其转换为指定类型的能力筛选值。 | 不适用。 | Enumerable.OfType |
ToArray | 将集合转换为数组。 此方法强制执行查询。 | 不适用。 | Enumerable.ToArray |
ToDictionary | 根据键选择器函数将元素放入 Dictionary<TKey,TValue>。 此方法强制执行查询。 | 不适用。 | Enumerable.ToDictionary |
ToList | 将集合转换为 List<T>。 此方法强制执行查询。 | 不适用。 | Enumerable.ToList |
ToLookup | 根据键选择器函数将元素放入 Lookup<TKey,TElement>(一对多字典)。 此方法强制执行查询。 | 不适用。 | Enumerable.ToLookup |
查询表达式语法示例
下面的代码示例使用显式类型化的范围变量将类型转换为子类型,然后才访问仅在此子类型上可用的成员。
class Plant
{
public string Name { get; set; }
} class CarnivorousPlant : Plant
{
public string TrapType { get; set; }
} static void Cast()
{
Plant[] plants = new Plant[] {
new CarnivorousPlant { Name = "Venus Fly Trap", TrapType = "Snap Trap" },
new CarnivorousPlant { Name = "Pitcher Plant", TrapType = "Pitfall Trap" },
new CarnivorousPlant { Name = "Sundew", TrapType = "Flypaper Trap" },
new CarnivorousPlant { Name = "Waterwheel Plant", TrapType = "Snap Trap" }
}; var query = from CarnivorousPlant cPlant in plants
where cPlant.TrapType == "Snap Trap"
select cPlant; foreach (Plant plant in query)
Console.WriteLine(plant.Name); /* 输出: Venus Fly Trap
Waterwheel Plant
*/
}
串联是指将一个序列附加到另一个序列的操作。
下图描绘了两个字符序列的串联操作。
下面一节列出了执行串联的标准查询运算符方法。
方法
Concat | 连接两个序列以组成一个序列。 | 不适用。 | Enumerable.Concat |
聚合运算从值的集合中计算出单个值。 例如,从一个月累计的每日温度值计算出日平均温度值就是一个聚合运算。
下图显示对数字序列进行两种不同聚合操作所得结果。 第一个操作累加数字。 第二个操作返回序列中的最大值。
下节列出了执行聚合运算的标准查询运算符方法。
方法
聚合 | 对集合的值执行自定义聚合运算。 | 不适用。 | Enumerable.Aggregate |
平均值 | 计算值集合的平均值。 | 不适用。 | Enumerable.Average |
计数 | 对集合中元素计数,可选择仅对满足谓词函数的元素计数。 | 不适用。 | Enumerable.Count |
LongCount | 对大型集合中元素计数,可选择仅对满足谓词函数的元素计数。 | 不适用。 | Enumerable.LongCount |
最大值 | 确定集合中的最大值。 | 不适用。 | Enumerable.Max |
最小值 | 确定集合中的最小值。 | 不适用。 | Enumerable.Min |
Sum | 对集合中的值求和。 | 不适用。 | Enumerable.Sum |