深入理解C#(第3版)-- 【附录】附录A LINQ标准查询操作符(学习笔记)
A.1 聚合
聚合操作符,所有的结果只有一个值而不是一个序列。
表A-1 聚合操作符示例
表 达 式 结 果
numbers.Sum() 10
numbers.Count() 5
numbers.Average() 2
numbers.LongCount(x => x % 2 == 0) 3(long类型;有3个偶数)
words.Min(word => word.Length) 3("one" 和"two" )
words.Max(word => word.Length) 5("three")
numbers.Aggregate("seed", (current,item) => current + item, "SEED01234"
result => result.ToUpper())
A.2 连接
只存在一个连接操作符:Concat ,使用延迟执行模式,并且均为流式数据。
表A-2 Concat示例
表 达 式 结 果
numbers.Concat(new[] {2, 3, 4, 5, 6}) 0, 1, 2, 3, 4, 2, 3, 4, 5, 6
A.3 转换
object[] allStrings = {"These", "are", "all", "strings"}; object[] notAllStrings = {"Number", "at", "the", "end", 5};
表A-3 转换示例
表 达 式 结 果
|
allStrings.Cast<string>() allStrings.OfType<string>() notAllStrings.Cast<string>() notAllStrings.OfType<string>() numbers.ToArray() numbers.ToList() words.ToDictionary (w =>w.Substring(0, 2))
// 键是这个单词的第一个字母 words.ToLookup(word => word[0])
words.ToDictionary(word => word[0]) |
"These", "are", "all", "strings"(作为IEnumerable<string> ) "These", "are", "all", "strings"(作为IEnumerable<string> ) 遍历时遇到转换失败的地方,会抛出异常 "Number", "at", "the", "end" (作为IEnumerable<string>) 0, 1, 2, 3, 4 (作为int[] ) 0, 1, 2, 3, 4 (作为List<int>) Dictionary中的内容: "ze": "zero" "on": "one" "tw": "two" "th": "three" "fo": "four" Lookup中的内容: 'z': "zero" 'o': "one" 't': "two", "three" 'f': "four" 异常:每个键只能有一个数据项,所以在遇到't' 时转换失败 |
ToArray和ToList 的含义显而易见:它们读取整个序列到内存中,并把结果作为一个数组或一个List<T>返回。两者都是立即执行。
Cast和OfType 把一个非类型化序列转换为类型化的,或抛出异常(对于Cast),或忽略那些不能隐式转换为输出序列元素类型的输入序列元素(对于OfType)。这个运算符也能用于把某个类型化序列转换为更加具体的类型化序列,例如把IEnumerable<object> 转换为IEnumerable<string>。转换以流的方式延迟执行。
ToDictionary和ToLookup 都使用委托来获得任何特定元素的键。ToDictionary返回一个把键映射到元素类型的字典,而ToLookup 返回相应的类型化的ILookup<,>。查找类似于查字典,只不过和健相关的值不是一个元素而是元素的序列。查找通常用于普通操作中希望有重复的键存在的时候,而重复的键会引起ToDictionary抛出异常。两者更复杂的重载方法可以将自定义的IEqualityComparer<T>用于比较键的操作,并在每个元素被放到字典或者开始查找之前,把转换委托应用于其上。另外,两个方法都会使用立即执行的模式。
没有提供AsEnumerable和AsQueryable 这两个操作符的例子,因为它们不会以一种显而易见的方式立即影响结果。但它们影响查询执行的方式。
思考一下如下查询表达式:
// Filter the users in the database with LIKE from user in context.Users where user.Name.StartsWith("Tim") select user; // Filter the users in memory from user in context.Users.AsEnumerable() where user.Name.StartsWith("Tim") select user;
第2个查询表达式强制编译时的源类型为IEnumerable<User>而非IQueryable <User>,因而所有的处理过程都可以在内存中而不必在数据库中完成。编译器将使用Enumerable的扩展方法(它获取委托参数)而不是Queryable的扩展方法(它获取表达式树参数)。
A.4 元素操作符
所有操作符都使用立即执行的模式。
表A-4 单个元素选取示例
|
表 达 式 |
结 果 |
|
words.ElementAt(2) words.ElementAtOrDefault(10) words.First() words.First(w => w.Length == 3) words.First(w => w.Length == 10) words.FirstOrDefault (w => w.Length == 10) words.Last() words.Single() words.SingleOrDefault() words.Single(word => word.Length == 5) words.Single(word => word.Length == 10) words.SingleOrDefault(w => w.Length == 10) |
"two" null "zero" "one" 异常:没有匹配的元素 null "four" 异常:不止一个元素 异常:不止一个元素 "three" 异常:没有匹配的元素 null |
以上这些方法的OrDefault版本都不会抛出异常,而是返回元素类型的默认值。但有一种例外情况:如果序列为空(empty ), SingleOrDefault将返回默认值,但如果序列中的元素不止一个,将抛出异常,就像Single 方法一样。该方法适用于所有条件正确,序列只包含0 或1 个元素的情况。
A.5 相等操作
只有一个标准的相等操作符:SequenceEqual (见表A-5)。它是按照顺序逐一比较两个序列中的元素是否相等。
返回值就是一个Boolean值,并使用立即执行的模式来计算。
表A-5 序列相等运算示例
|
表 达 式 |
结 果 |
|
words.SequenceEqual (new[]{"zero","one", "two","three","four"}) words.SequenceEqual (new[]{"ZERO","ONE","TWO","THREE","FOUR"}) words.SequenceEqual (new[]{"ZERO","ONE", "TWO","THREE","FOUR"}, StringComparer.OrdinalIgnoreCase) |
True False True |
A.6 生成
在所有的生成操作符(见表A-6)中,只有一个会对现有的序列进行处理:DefaultIfEmpty。
如果序列不为空,就返回原始序列,否则返回含有单个元素的序列。其中的元素通常是序列类型的默认值,不过重载方法允许你设定要使用的值。
表A-6 生成操作符示例
|
表 达 式 |
结 果 |
|
numbers.DefaultIfEmpty() new int[0].DefaultIfEmpty() new int[0].DefaultIfEmpty(10) Enumerable.Range(15, 2) Enumerable.Repeat(25, 2) Enumerable.Empty<int>() |
0, 1, 2, 3, 4 0(包含在一个IEnumerable<int>类型的序列中) 10(包含在一个IEnumerable<int>类型的序列中) 15, 16 25, 25 一个类型为IEnumerable<int>的空序列 |
其他3个生成操作符仅仅是Enumerable的静态方法。
Range生成一个整数序列,通过参数可以设定第一个值和需要生成值的个数。
Repeat 根据指定的次数来重复特定的单个值,以生成任意类型的序列。
Empty生成任意类型的空序列。
所有生成操作符都使用延迟执行,并对结果进行流式处理。也就是说,它们不会预先生成集合并返回。不过,返回正确类型的空数组的Empty方法是个例外。一个空的数组是完全不可变的,因此相同元素类型的所有这种调用,都将返回相同的空数组。
A.7 分组
有两个分组操作符,其中一个就是ToLookup, 另一个就是GroupBy。GroupBy使用延迟执行,不过会缓存结果。
表A-7 GroupBy示例
|
表 达 式 |
结 果 |
|
words.GroupBy(word => word.Length)
words.GroupBy (word => word.Length, // 键 word => word.ToUpper() // 分组元素 )
// Project each (key, group) pair to string words.GroupBy (word => word.Length, (key, g) => key + ": " + g.Count()) |
Key: 4; Sequence: "zero", "four" Key: 3; Sequence: "one", "two" Key: 5; Sequence: "three"
Key: 4; Sequence: "ZERO", "FOUR" Key: 3; Sequence: "ONE", "TWO" Key: 5; Sequence: "THREE"
"4: 2", "3: 2", "5: 1" |
A.8 连接
有两个操作符用于连接,即Join和GroupJoin,两者都是延迟执行,并流处理左边序列,而对于右边序列,在请求第一个结果时便读取其全部内容。
表A-8 Join示例
|
表 达 式 |
结 果 |
|
names.Join // 左边序列 (colors, // 右边序列 name => name[0], // 左边键选择器 color => color[0], // 右边键选择器 // 为结果对投影 (name, color) => name+" - " + color )
Names.GroupJoin (colors, name => name[0], color => color[0], // 为键/序列对投影 (name, matches) => name + ": " + string.Join("/", matches.ToArray()) ) |
"Robin - Red", "Ruth - Red", "Bob - Blue", "Bob - Beige"
"Robin: Red", "Ruth: Red", "Bob: Blue/Beige", "Emma: " |
A.9 分部
分部操作符中,既可以跳过(skip )序列的开始部分,只返回剩余元素,也可以只获取(take )序列的开始部分,而忽略剩余元素。
所有的分部操作符都是延迟执行和流式数据。
表A-9 分部示例
|
表 达 式 |
结 果 |
|
words.Take(2) words.Skip(2) words.TakeWhile(word => word.Length <= 4) words.SkipWhile(word => word.Length <= 4) |
"zero", "one" "two","three", "four" "zero", "one", "two" "three","four" |
A.10 投影
两个投影操作符(Select 和SelectMany)。 Select 是一种简单的从源元素到结果元素的一对一投影。SelectMany在查询表达式中有多个from子句的时候
使用;原始序列中的每个元素都用来生成新的序列。
两个投影操作符(见表A-10)都是延迟执行。
表A-10 投影示例
|
表 达 式 |
结 果 |
|
words.Select(word => word.Length)
words.Select ((word, index) => index.ToString() + ": " +word)
words.SelectMany (word => word.ToCharArray())
words.SelectMany ((word, index) => Enumerable.Repeat(word, index)) |
4, 3, 3, 5, 4
"0: zero", "1: one", "2: two", "3: three", "4: four"
'z', 'e', 'r', 'o', 'o', 'n', 'e', 't', 'w', 'o', 't', 'h', 'r', 'e', 'e', 'f', 'o', 'u', 'r'
"one", "two", "two", "three", "three","three", "four", "four","four","four" |
.NET 4引入了一个新的操作符Zip 。
它包含两个序列,并对每个元素对应用指定的投影:先是每个序列的第一个元素,然后是每个序列的第二个元素,以此类推。任何一个源序列达到末尾时,结果序列都将停止产生。
表A-11 ZiP示例
|
表 达 式 |
结 果 |
|
names.Zip(colors, (x, y) => x+ "-" +y)
// 第二个序列提前停止生成 names.Zip(colors.Take(3), (x, y) => x + "-" + y |
"Robin-Red", "Ruth-Blue", "Bob-Beige", "Emma-Green"
"Robin-Red", "Ruth-Blue", "Bob-Beige" |
A.11 数量
数量操作符(见表A-12)都返回一个Boolean值,使用立即执行。
All 操作符检查在序列中的所有元素是否满足指定的条件。
Any 操作符检查在序列中的任意元素是否满足指定的谓词,如果使用没有参数的重载,则检查序列中是否存在元素。
Contains 操作符检查序列是否包含特殊的元素,可选设置要使用的比较方式。
表A-12 数量示例
|
表 达 式 |
结 果 |
|
words.All(word => word.Length > 3) words.All(word => word.Length > 2) words.Any() words.Any(word => word.Length == 6) words.Any(word => word.Length == 5) words.Contains("FOUR") words.Contains("FOUR", StringComparer.OrdinalIgnoreCase) ) |
false ("one" 和"two" 的确包含3个字母) true true(序列不为空) false (没有6个字母的单词) true("three"满足这个条件) false true |
A.12 过滤
两个过滤操作符是OfType和Where。
Where总是使用延迟执行和流式数据
表A-13 过滤示例
|
表 达 式 |
结 果 |
|
words.Where(word => word.Length > 3)
words.Where ((word, index) => index < word.Length) |
"zero", "three", "four"
"zero", // index=0, length=4 "one", // index=1, length=3 "two", // index=2, length=3 "three", // index=3, length=5 // Not "four", index=4, length=4 |
A.13 基于集的操作符
所有的集合运算符都是延迟执行。
Distinct 操作符最简单——它只对单个序列起作用,并且返回所有不重复元素(已经排除了重复项)的新序列。其他的运算符也确保只返回不重复的元素,不过它们对两个序列起作用。
Intersect返回在两个序列中都出现的元素。
Union返回出现在任一序列中的元素。
Except 返回出现在第一个序列,但不出现在第二个序列中的元素(出现第二个序列但不在第一个中的元素也不返回)。
表A-14 基于集合的操作符示例
|
表 达 式 |
结 果 |
|
abbc.Distinct() abbc.Intersect(cd) abbc.Union(cd) abbc.Except(cd) cd.Except(abbc) |
"a", "b", "c" "c" "a", "b", "c", "d" "a", "b" "d" |
所有这些操作符都使用延迟执行,但有的使用了缓冲,有的使用了流式处理,缓冲和流式处理显然更复杂一些。Distinct 和Union都对输入序列进行了流式处理,而Intersect和Except先是读取整个右边输入序列,然后像连接操作符那样对左边输入序列进行流式处理。所有这些操作符都保存已返回元素的集,以确保不返回重复的元素。这意味着即使是Distinct 和Union,也不适合那些过大而不适于放入内存的序列,除非你知道经过处理后的集并不会包含很多元素。
A.14 排序
OrderBy和OrderByDescending提供了“主要的”排序方式,而ThenBy和ThenByDescending 提供了次要的排序方式,用以区别使用主要的排序方式无法区别的元素。在每种情况中,都要指定从元素到排序键的投影,也指定键之间的比较。不像框架中的其他排序算法(比如List<T>.Sort),LINQ排序比较稳定——换句话说,如果两个元素根据它们的排序关键字被认为是相等的,那么将按照它们在原始序列中的顺序返回。
最后一个排序操作符是Reverse,它仅反转序列的顺序。所有的排序操作符(见表A-15)都是延迟执行,不过会缓存其中的数据。
表A-15 排序示例
|
表 达 式 |
结 果 |
|
words.OrderBy(word => word)
// 依据第二个字母对单词排序 words.OrderBy(word => word[1])
// 依据长度对单词排序,如长度相同则以原顺序返回 words.OrderBy(word => word.Length)
words.OrderByDescending (word => word.Length)
// 依据长度排序,如长度相同则以首字母顺序排序 words.OrderBy(word => word.Length) .ThenBy(word => word)
// 依据长度排序,如长度相同则按反向字母顺序排序 words.OrderBy(word => word.Length) .ThenByDescending(word => word)
words.Reverse() |
"four", "one","three", "two", "zero"
"zero", "three","one", "four", "two"
"one", "two","zero", "four", "three"
"three", "zero", "four", "one", "two"
"one", "two","four", "zero", "three"
"two", "one","zero", "four", "three"
"four", "three", "two", "one", "zero" |
浙公网安备 33010602011771号