“标准查询运算符”是组成语言集成查询 (LINQ) 模式的方法。大多数这些方法都在序列上运行,其中的序列是一个对象,其类型实现了
IEnumerable<T>
接口或
IQueryable<T>
接口。标准查询运算符提供了包括
筛选、投影、聚合、排序
等功能在内的查询功能。
各个标准查询运算符在
执行时间
上有所不同,具体情况取决于它们是返回
单一值
还是
值序列
。返回单一值的方法(例如
Average
和
Sum
)会
立即执行
。返回序列的方法会
延迟查询执行
,并返回一个可枚举的对象。
对于在内存中集合上运行的方法(即扩展
IEnumerable<T>
的方法),返回的可枚举对象将捕获传递到方法的参数。在枚举该对象时,将使用查询运算符的逻辑,并返回查询结果。
一、按标准执行方式分类
标准查询运算符方法的 LINQ to Objects 实现采用两种主要方式之一来执行:
立即执行
和
延迟执行
。采用延迟执行的查询运算符可以进一步分为两类:
流式
和
非流式
。
1.执行方式
(1)
立即
: 立即执行意味着在代码中声明查询的位置读取数据源并执行运算。
返回单个不可枚举的结果的所有标准查询运算符都立即执行
。
采用延迟执行方式的查询运算符可以另外分类为
流式
和
非流式
。
①
流式运算符不需要在生成元素前读取所有源数据
。在执行时,流式运算符一边读取每个源元素,一边对该源元素执行运算,并在可行时生成元素。流式运算符将持续读取源元素直到可以生成结果元素。这意味着可能要读取多个源元素才能生成一个结果元素。
②
非流式运算符必须读取所有源数据才能生成结果元素
。诸如
排序
和
分组
等运算属于此类别。在执行时,非流式查询运算符读取所有源数据,将其放入数据结构中,执行运算,然后生成结果元素。
二、排列数据
排序操作按一个或多个特性对序列的元素进行排序。第一个排序条件对元素执行主要排序。通过指定第二个排序条件,可以对各个主要排序组中的元素进行排序。
下图演示对一个字符序列执行按字母排序操作的结果。
1 var words = new[] { "the", "quick", "brown", "fox", "jumps" };
2 var query = from word in words
3 orderby word.Length
4 select word;
6 foreach (var word in query)
8 Console.WriteLine(word);
下面通过演示使用 orderby 进行升序排序:按字符串长度
1 var words = new[] { "the", "quick", "brown", "fox", "jumps" };
2 var query = from word in words
3 orderby word.Substring(0,1) descending
4 select word;
6 foreach (var word in query)
8 Console.WriteLine(word);
下面通过演示使用 orderby descending 进行降序排序:按字符串的第一个字母
1 var words = new[] { "the", "quick", "brown", "fox", "jumps" };
2 var query = from word in words
3 orderby word.Length, word.Substring(0, 1)
4 select word;
6 foreach (var word in query)
8 Console.WriteLine(word);
下面通过演示使用 orderby 进行主要和次要排序:先升序按字符串长度(主)、再升序按字符串的第一个字母(次)
1 var words = new[] { "the", "quick", "brown", "fox", "jumps" };
2 var query = from word in words
3 orderby word.Length, word.Substring(0, 1) descending
4 select word;
6 foreach (var word in query)
8 Console.WriteLine(word);
下面通过演示使用 orderby descending 进行主要和次要排序:先升序按字符串长度(主)、再降序按字符串的第一个字母(次)
三、Set 操作
LINQ 中的 Set 操作是指根据相同或不同集合中是否存在等效元素来生成结果集的查询操作。
标准查询运算符操作方法 - 排序
|
C# 查询表达式语法
|
OrderBy
|
按升序对值进行排序。
|
orderby
|
OrderByDescending
|
按降序对值进行排序。
|
orderby … descending
|
ThenBy
|
按升序执行次要排序。
|
orderby …, …
|
ThenByDescending
|
按降序执行次要排序。
|
orderby …, … descending
|
Reverse
|
颠倒集合中的元素的顺序。
|
C# 查询表达式语法
1 string[] words = { "the", "quick", "brown", "fox", "jumps" };
3 var query = from word in words
4 where word.Length == 3
5 select word;
7 foreach (var word in query)
9 Console.WriteLine(word);
使用 where 子句来从数组中筛选那些具有特定长度的字符串
六、投影操作
投影是指将对象转换为一种新形式的操作,该形式通常只包含那些将随后使用的属性。通过使用投影,您可以构建依据每个对象生成的新类型。您可以映射属性,并对该属性执行数学函数。还可以在不更改原始对象的情况下映射该对象。
1 var words = new[] { "the", "quick", "brown", "fox", "jumps" };
2 var query = from word in words
3 select word.Substring(0,1);
5 foreach (var word in query)
7 Console.WriteLine(word);
Select:下面的示例使用 select 子句来映射字符串列表中每个字符串的第一个字母
1 var phrases = new List<string>() { "an apple a day", "the quick brown fox
" };
3 var query = from phrase in phrases
4 from word in phrase.Split(' ')
5 select word;
7 foreach (var word in query)
9 Console.WriteLine(word);
SelectMany:下面的示例使用多个 from 子句来映射字符串列表中每个字符串中的每个单词
Select() 和 SelectMany() 的工作都是依据源值生成一个或多个结果值。Select() 为每个源值生成一个结果值。因此,总体结果是一个与源集合具有相同元素数目的集合。与之相反,SelectMany() 将生成单一总体结果,其中包含来自每个源值的串联子集合。作为参数传递到 SelectMany() 的转换函数必须为每个源值返回一个可枚举值序列。然后,SelectMany() 将串联这些可枚举序列以创建一个大的序列。
下面两个插图演示了这两个方法的操作之间的概念性区别。在每种情况下,假定选择器(转换)函数从每个源值中选择一个由花卉数据组成的数组。
下图描述 Select() 如何返回一个与源集合具有相同元素数目的集合。
下图描述 SelectMany() 如何将中间数组序列串联为一个最终结果值,其中包含每个中间数组中的每个值。
下面的示例比较 Select() 和 SelectMany() 的行为。代码将通过从源集合的每个花卉名称列表中提取前两项来创建一个“花束”。在此示例中,转换函数 Select 使用的“单一值”本身就是一个值集合。这需要额外的 foreach 循环,以便枚举每个子序列中的每个字符串。
1 static void Main(string[] args)
3 var bouquets = new List<Bouquet>()
5 new Bouquet {Flowers = new List<string> {"sunflower", "daisy", "daffodil", "larkspur"}},
6 new Bouquet {Flowers = new List<string> {"tulip", "rose", "orchid"}},
7 new Bouquet {Flowers = new List<string> {"gladiolis", "lily", "snapdragon", "aster", "protea"}},
8 new Bouquet {Flowers = new List<string> {"larkspur", "lilac", "iris", "dahlia"}}
9 };
11 IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);
12 IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);
14 Console.WriteLine("query1 - Select():");
15 foreach (IEnumerable<string> collection in query1)
16 {
17 foreach (var item in collection)
18 {
19 Console.WriteLine(item);
20 }
21 }
23 Console.WriteLine("\nquery2 - SelectMany():");
24 foreach (var item in query2)
25 {
26 Console.WriteLine(item);
27 }
29 Console.Read();
30 }
32 class Bouquet
33 {
34 public List<string> Flowers { get; set; }
View Code
七、划分数据
LINQ 中的分区指的是在不重新排列元素的情况下,将输入序列划分为两部分,然后返回其中一个部分的操作。
下图显示对一个字符序列执行三个不同的分区操作的结果。第一个操作返回序列中的前三个元素。第二个操作跳过前三个元素,返回剩余的元素。第三个操作跳过序列中的前两个元素,返回接下来的三个元素。
标准查询运算符操作方法 - 投影
|
C# 查询表达式语法
|
运算符名称
C# 查询表达式语法
将两个数据源
“联接”
就是将一个数据源中的对象与另一个数据源中共享某个通用特性的对象关联起来。
当查询所面向的数据源相互之间具有无法直接领会的关系时,联接就成为一项重要的运算。在面向对象的编程中,这可能意味着在未建模对象之间进行关联,例如对单向关系进行反向推理。下面是单向关系的一个示例:Customer 类有一个类型为 City 的属性,但 City 类没有作为 Customer 对象集合的属性。如果你具有一个 City 对象列表,并且要查找每个城市中的所有客户,则可以使用联接运算完成此项查找。
LINQ 框架中提供的联接方法包括
Join
和
GroupJoin
。这些方法执行同等联接,即根据两个数据源的键是否相等来匹配这两个数据源的联接。(与此相较,Transact-SQL 支持除“等于”之外的联接运算符,例如“小于”运算符。)用关系数据库术语表达,就是说
Join
实现了内部联接,这种联接只返回那些在另一个数据集中具有匹配项的对象。
GroupJoin
方法在关系数据库术语中没有直接的等效项,但它实现了内部联接和左外部联接的超集。左外部联接是这样一种联接:它返回第一个(左)数据源的每个元素,即使该元素在另一个数据源中没有关联元素。
下图显示了一个概念性视图,其中包含两个集合以及这两个集合中的包含在内部联接或左外部联接中的元素。
C# 查询表达式语法
根据键选择器函数联接两个序列并提取值对。
join … in … on … equals …
1 var numbers = new List<int>() { 35, 44, 200, 84, 3987, 4, 199, 329, 446, 208 };
3 IEnumerable<IGrouping<bool, int>> query = from number in numbers
4 group number by number % 2 == 0;
6 foreach (var group in query)
8 Console.WriteLine($"{(group.Key ? "偶数" : "基数")}:");
9 foreach (var i in group)
10 {
11 Console.WriteLine(i);
12 }
使用 group by 子句根据列表中的整数是奇数还是偶数进行分组
十、生成操作
生成是指创建新的值序列。
C# 查询表达式语法
转换方法
更改输入对象的类型
。
LINQ 查询中的转换运算可用于各种应用程序。下面是一些示例:
(1)
Enumerable
.AsEnumerable
<TSource>
方法可用于隐藏类型的标准查询运算符的自定义实现。
(2)
Enumerable
.OfType
<TResult>
方法可用于启用非参数化集合以进行 LINQ 查询。
(3)
Enumerable
.ToArray
<TSource>
、
Enumerable
.ToDictionary
<TSource, TKey>
、
Enumerable
.ToList
<TSource>
和
Enumerable
.ToLookup
<TSource, TKey>
方法可用于强制立即执行查询,而非推迟到枚举查询时。
使用显式类型化的范围变量。
例如:
from string str in words
3
var
plants =
new
Plant[]
5
new
CarnivorousPlant {Name =
"
Venus Fly Trap
"
, TrapType =
"
Snap Trap
"
},
6
new
CarnivorousPlant {Name =
"
Pitcher Plant
"
, TrapType =
"
Pitfall Trap
"
},
7
new
CarnivorousPlant {Name =
"
Sundew
"
, TrapType =
"
Flypaper Trap
"
},
8
new
CarnivorousPlant {Name =
"
Waterwheel Plant
"
, TrapType =
"
Snap Trap
"
}
9
};
11
var
query =
from
CarnivorousPlant plant
in
plants
12
where
plant.TrapType ==
"
Snap Trap
"
13
select
plant;
15
foreach
(
var
carnivorousPlant
in
query)
16
{
17
Console.WriteLine(carnivorousPlant.Name);
18
}
20
Console.Read();
21
}
23
class
Plant
24
{
25
public
string
Name {
get
;
set
; }
26
}
28
class
CarnivorousPlant : Plant
29
{
30
public
string
TrapType {
get
;
set
; }
使用显式类型化的范围变量将类型强制转换为子类型,然后才访问仅在此子类型中提供的成员。
十四、串联操作
串联是指将一个序列
追加
到另一个序列的运算。
下图演示对两个字符序列执行的串联运算。
C# 查询表达式语法
入门:《
走进 LINQ 的世界
》
进阶:《
LINQ 标准查询操作概述
》
(强烈推荐)
技巧:《
Linq To Objects - 如何操作字符串
》 和 《
Linq To Objects - 如何操作文件目录
》
【参考】https://msdn.microsoft.com/zh-cn/library/bb397896(v=vs.100).aspx
【来源】部分图片摘自微软官方文档