第9章 LINQ 运算符
第9章 LINQ 运算符
本章所有例子所使用的 names 数组都是一致的:
string[] names = {"Tom", "Dick", "Harry", "Marry", "Jay" };
9.1 概述
标准查询运算符分三类:
- 输入是序列(
IEnumerable),输出是序列(IEnumerable)(IEnumerable →IEnumerable) - 输入是序列(
IEnumerable),输出是单个元素或标量值(IEnumerable → value) - 没有输入,输出是序列(void →
IEnumerable)
运算符分类
IEnumerable → IEnumerable
筛选
映射
连接
排序
分组
集合
转换
输入
输出
IEnumerable → value
提取元素
聚合
“断言”<br/>(量词)
void → IEnumerable
生成集合
9.1.1 序列 → 序列(IEnumerable → IEnumerable)
9.1.1.1 筛选运算符
IEnumerable<TSource> → IEnumerable<TSource>
此类运算符返回原始序列的 子集 :
Where 、 Take 、 TakeWhile 、 Skip 、 SkipWhile 、 Distinct
9.1.1.2 映射运算符
IEnumerable<TSource> → IEnumerable<TResult>
此类运算符将数据 映射为另一种形式 :
Select、SelectMany
9.1.1.3 连接运算符
IEnumerable<TOuter>,IEnumerable<TInner> → IEnumerable<TResult>
此类运算符用于连接元素:
Join 、 GroupJoin 、 Zip
9.1.1.4 排序运算符
IEnumerable<TSource> → IOrderedEnumerable<TSource>
此类运算符将返回已排序的序列:
OrderBy 、 ThenBy 、 Reverse
9.1.1.5 分组运算符
IEnumerable<TSource> → IEnumerable<IGrouping<TKey, TElement>>
此类运算符用于对序列 分组 :
GroupBy
9.1.1.6 集合运算符
IEnumerable<TSource>,IEnumerable<TSource> → IEnumerable<TSource>
此类运算符用于 合并 序列:
Concat 、 Union 、 Intersect 、 Except
9.1.1.7 转换方法:导入
IEnumerable → IEnumerable<TResult>
此类运算符类似 Select,却仅进行转换:
OfType、Cast
9.1.1.8 转换方法:导出
IEnumerable<TSource> → 数组、列表、字典、lookup 或 IEnumerable<T>
此类运算符用于将 IEnumerable 转化为具体的 集合 类型:
ToArray、ToList、ToDictionary、ToLookUp、AsEnumerable、AsQueryable
9.1.2 序列 → 元素或标量值(IEnumerable → value)
9.1.2.1 元素运算符
IEnumerable<TSource> → TSource
此类运算符用于 取出一个特定的元素 :
First、FirstOrDefault、Last、LastOrDefault、Single、SingleOrDefault、ElementAt、ElementAtOrDefault、DefaultIfEmpty
9.1.2.2 聚合方法
IEnumerable<TSource> → scalar
此类运算符对元素进行 计算 ,并返回 一个标量 值:
Aggregate 、 Average 、 Count 、 LongCount 、 Sum 、 Max 、 Min
9.1.2.3 量词运算符
IEnumerable<TSource> → bool
此类运算符返回 true 或 false:
All 、 Any 、 Contains 、 SequenceEqual
9.1.3 void → 序列(void → IEnumerable)
9.1.3.1 生成集合的方法
void → IEnumerable<TResult>
此类运算符可以从零开始输出一个 IEnumerable:
Empty 、 Range 、 Repeat
9.2 筛选
| 方法 | 描述 | 等价 SQL |
|---|---|---|
Where |
返回满足给定条件的元素集合 |
WHERE |
Take |
返回前 count 个元素,丢弃剩余的元素 | WHERE ROW_NUMBER()... 或 TOP n subqery |
Skip |
跳过前 count 个元素,返回剩余的元素 | WHERE ROW_NUMBER()... 或 NOT IN (SELECT TOP n ...) |
TakeWhile |
返回输入序列中的元素,直到 predicate 判断为 false | 抛出异常 |
SkipWhile |
持续忽略输入序列中的元素,直至 predicate 为 false 。而后返回剩余元素 | 抛出异常 |
Distinct |
返回一个 没有重复元素的 集合 | SELECT DISTINCT ... |
9.2.1 Where
内部实现如下:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
foreach (TSource element in source)
if (predicate(element))
yield return element;
}
Where 支持两种 Func(L2S 和 EF 只支持第一种): Func<TSource> 和 Func<TSource, int> ,其 int 参数标注当前 TSource 元素是第几个。例如:
names.Where ((n, i) => i % 2 == 0).Dump ("Skipping every second element");
9.2.1.5 #suspend#
后面讲解 L2S 和 EF,暂时略过。
9.2.2 Take 和 Skip 运算符
Take 将返回序列中的前 n 个元素,并放弃其余元素;Skip 则是放弃前 n 个元素,并返回其余元素。
9.2.3 TakeWhile 和 SkipWhile
假设有如下数组:
int[] numbers = { 3, 5, 2, 234, 4, 1 };
TakeWhile 输出每一个元素,直至给定的断言为 false,并忽略剩余元素:
// 输出 3、5、2
numbers.TakeWhile (n => n < 100).Dump ("TakeWhile");
SkipWhile 忽略每一个元素,直至给定的断言为 false,并返回剩余的元素:
// 输出 234、4、1
numbers.SkipWhile (n => n < 100).Dump ("SkipWhile");
9.2.4 Distinct
Distinct 返回去除 重复元素 后的输入序列,并可以接受(可选的)自定义相等比较器。下面的例子返回去除重复字母的字符串:
// 输出 Helowrd
"HelloWorld".Distinct()
9.3 映射
| 方法 | 描述 | 等价 SQL |
|---|---|---|
Select |
将输入中的每一个元素按照给定 Lambda 表达式进行转换 | SELECT |
SelectMany |
将输入的每一个元素按照 Lambda 表达式进行转换,并将嵌套的集合 展平 后连接在一起 | INNER JOIN, LEFT OUTER JOIN, CROSS JOIN |
9.3.1 Select
内部实现如下:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach (TSource element in source)
yield return selector(element);
}
Select 支持两种 Func(L2S 和 EF 仅支持一种): Func<TSource, TResult> 和 Func<TSource, TResult, int> ,其 int 参数标注当前 TSource 元素是第几个。例如:
names.Select ((s,i) => i + "=" + s).Dump();
关联子查询
Select 常常用到关联子查询,当子查询引用 外部查询的 对象时,该子查询就是关联的。例如,如下代码子查询引用了 d:
string sampleDirectory = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
DirectoryInfo[] dirs = new DirectoryInfo (sampleDirectory).GetDirectories();
var query =
from d in dirs
where (d.Attributes & FileAttributes.System) == 0
select new
{
DirectoryName = d.FullName,
Created = d.CreationTime,
Files = from f in d.GetFiles()
where (f.Attributes & FileAttributes.Hidden) == 0
select new { FileName = f.Name, f.Length, }
};
Tips
老实说关联子查询和
SelectMany 有点相似。
9.3.1.6 #suspend#
后面讲解 L2S 和 EF,暂时略过。
9.3.2 SelectMany
内部实现如下:
public static IEnumerable<TResult> SelectMany<TSource,TResult>(IEnumerable<TSource> source, Func <TSource, IEnumerable<TResult>> selector)
{
foreach (TSource element in source)
foreach (TResult subElement in selector (element))
yield return subElement;
}
SelectMany 可用于展开子序列,将嵌套的集合平面化;或将两个集合合并。例如:
var fullNames = new[] { "Anne Williams", "John Fred Smith", "Sue Green" }.AsQueryable();
// 将字符串拆分,输出 Anne Williams John Fred Smith Sue Green
IEnumerable<string> query2 = fullNames.SelectMany (name => name.Split());
var numbers = new[] { 1, 2, 3 }.AsQueryable();
var letters = new[] { "a", "b" }.AsQueryable();
// 将两部分内容合并,输出 1a 1b 2a 2b 3a 3b
IEnumerable<string> query =
from n in numbers
from l in letters
select n.ToString() + l;
SelectMany 的查询语法
查询语法中不存在 SelectMany,需要使用 两层 from :
IEnumerable<string> query3 =
from fullName in fullNames
from name in fullName.Split() // 编译器自动转化为 SelectMany
select name;
查询语法相较流式语法,之前定义的变量仍能使用。以如下代码为例,该语句将转化为如下流式语法:
IEnumerable<string> query =
from fullName in fullNames
from name in fullName.Split()
select name + " came from " + fullName;
System.String[]
.SelectMany (
fullName => fullName.Split (new Char[0]),
(fullName, name) => new { fullName, name }
)
.Select (temp0 => ((temp0.name + " came from ") + temp0.fullName))
从这一点可以看到查询语法的简洁之处。
9.3.2.7 #suspend#
后面讲解 L2S 和 EF,暂时略过。
9.4 连接
| 方法 | 描述 | 等价的 SQL |
|---|---|---|
Join |
使用查找规则对两个集合的元素进行匹配,返回平面的结果集 | INNER JOIN |
GroupJoin |
同上,但返回层次化的结果集 | INNER JOIN, LEFT OUTER JOIN |
Zip |
同时依次枚举两个序列(像拉链一样),对每一对元素执行指定的函数 | 抛出异常 |
9.4.1 Join 和 GroupJoin
Join 和 GroupJoin 将两个输入序列连接为一个输出序列。Join 生成平面的输出,GroupJoin 生成层次化的输出。相较 Select(Many),(Group)Join 对本地 IEnumerable 效率更高。差别如下:
C7.0 核心技术指南 第7版.pdf - p466 - C7.0 核心技术指南 第 7 版-P466-20240412171315-wt32ctd
9.4.1.5 Join
语法如下:
\(join \quad 内部变量 \quad in \quad 内部序列 \quad on \quad 外部键表达式 \quad equals \quad 内部键表达式\)
Join 在查询时会将内部集合预加载至表中,因此效率更高。如下代码(Join 和 SelectMany)结果一致,Join 的效率更高:
var slowQuery =
from c in customers
from p in purchases
where c.ID == p.CustomerID
select c.Name + " bought a " + p.Description;
var fastQuery =
from c in customers
join p in purchases on c.ID equals p.CustomerID
select c.Name + " bought a " + p.Description;
Join 可以多次使用:
from c in Customers
join p in Purchases on c.ID equals p.CustomerID // first join
join pi in PurchaseItems on p.ID equals pi.PurchaseID // second join
select new { c.Name, p.Description, pi.Detail }
Eureka
Join 的本质其实是“筛选”,将两个序列键相同的部分筛选出来。
9.4.1.6 基于多个键的 Join
我们可以通过匿名类型在一个查询中基于多个键进行连接查询:
from s in stringProps
join b in builderProps on new { s.Name, s.PropertyType }
equals new { b.Name, b.PropertyType }
需要注意,两个匿名类型在 结构 上必须一致。
9.4.1.7 Join 在流式语法中的使用
流式语法和对应的查询语法如下
var fluentSyntax =
Customers.Join ( // outer collection
Purchases, // inner collection
c => c.ID, // outer key selector
p => p.CustomerID, // inner key selector
(c, p) => new // result selector
{
c.Name, p.Description, p.Price
}
);
var querySyntax =
from c in Customers
join p in Purchases on c.ID equals p.CustomerID
select new
{
c.Name, p.Description, p.Price
};
可以看到,查询语法更为简洁。
使用流式语法时,若希望在
Join 之后添加其他子句(如OrderBy),则必须引入一个 匿名类型 。例如:var querySyntax =
from c in Customers
join p in Purchases on c.ID equals p.CustomerID
orderby p.Price
select c.Name + " bought a " + p.Description + " for $" + p.Price;
var fluentSyntax =
Customers.Join ( // outer collection
Purchases, // inner collection
c => c.ID, // outer key selector
p => p.CustomerID, // inner key selector
(c, p) => new { c, p } // result selector
)
.OrderBy (x => x.p.Price)
.Select (x => x.c.Name + " bought a " + x.p.Description + " for $" + x.p.Price);
9.4.1.8 GroupJoin #delay# 看不懂,略过
在查询语法中,GroupJoin 和 Join 一致,但需要和 into 关键字配合使用:
from c in Customers.AsEnumerable()
join p in Purchases on c.ID equals p.CustomerID
into custPurchases
select custPurchases
C7.0 核心技术指南 第7版.pdf - p469 - C7.0 核心技术指南 第 7 版-P469-20240413155101-k4ab81b
9.4.2 Zip 运算符
Zip 运算符就像拉链,同时枚举两个集合中的元素,返回经过处理的元素对。用法如下:
int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
// 产生序列:3=three 5=five 7=seven
IEnumerable<string> zip = numbers.Zip (words, (n, w) => n + "=" + w);
序列中不匹配的元素会 忽略 。
Error
L2S 和 EF 不支持
Zip
Info
另见Enumerate & Zip
9.5 排序
| 方法 | 描述 | 等价 SQL |
|---|---|---|
OrderBy ThenBy |
将序列按升序排序 | ORDER BY |
OrderByDescendingThenByDescending |
将序列按降序排序 | ORDER BY ... DESC |
Reverse |
将一个序列进行反转 | 抛出异常 |
9.5.1 OrderBy、OrderByDescending、ThenBy 和 ThenByDescending
9.5.1.1 OrderBy
OrderBy 根据委托的返回值确定元素先后,以如下代码为例:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
names.OrderBy (s => s.Length).Dump ("Ordered by length");
// 排序结果
Tom Jay Dick Mary Harry
9.5.1.2 ThenBy
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
names.OrderBy (s => s.Length).Dump ("Ordered by length");
可以注意到,Tom 和 Jay,Dick 和 Mary 字符长度相同,顺序不确定。此时我们可以使用 ThenBy 进行二次排序:
names.OrderBy (s => s.Length).ThenBy (s => s)
.Dump ("By length, then alphabetically");
对于 ThenBy 二次排序后仍未确定顺序的元素,可以用 ThenBy 再次排序。ThenBy 可以套用多次,如下两段代码等价:
names.OrderBy (s => s.Length).ThenBy (s => s[1]).ThenBy (s => s[0])
.Dump ("By length, then second character, then first character");
(
from s in names
orderby s.Length, s[1], s[0]
select s
)
.Dump ("Same query in query syntax");
9.5.1.3 比较和核对
如果我们想自定义排序方式,可以传入 IComparer 对象至运算符中:
names.OrderBy (n => n, StringComparer.CurrentCultureIgnoreCase)
.Dump ("Case insensitive ordering");
注意:查询语法、L2S 和 EF 不支持自定义比较器。
IOrderedEnumerable 和 IOrderedQueryable
上述运算符返回类型均为 IOrderedEnumerable<TSource> ,其内部保留了排序信息,以支持 Thenby 二次排序。
9.6 分组
9.6.1 GroupBy
查询语法如下:
\(group \quad 元素表达式 \quad by \quad 键表达式\)
GroupBy 可以对一个平面输入序列进行 分组 ,它在内部创建了一个临时的 列表字典 。它的返回类型是 IEnumerable<IGrouping<TKey, TElement>> ,用法如下:
string[] files = Directory.GetFiles (Path.GetTempPath()).Take (100).ToArray();
files.GroupBy (file => Path.GetExtension (file))
.Dump ("Your temporary files, grouped by extension.");
9.6.1.1 keySelector 和 elementSelector
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<this TSource, TKey, TElement>
(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector)
对于组内的元素,我们可以通过 elementSelector 委托修改后再分组。如下代码将群组中的元素转为了大写:
files.GroupBy (file => Path.GetExtension (file), file => file.ToUpper())
.Dump ("In Fluent Syntax");
Question
Join 的 Selector 和GroupBy 的 Selector 有什么区别?
9.6.1.2 GroupBy 的查询表达式
如下两段代码等价:
(
from file in files
group file.ToUpper() by Path.GetExtension (file)
)
.Dump ("In query syntax");
files.GroupBy (file => Path.GetExtension (file), file => file.ToUpper())
.Dump ("In Fluent Syntax");
正如在 8.3 查询表达式提到:
查询表达式一般以
from 子句开始,最后以 select 或者 group 子句结束。
若想在 GroupBy 后继续查询,需要使用 into 关键字:
from file in files
group file.ToUpper() by Path.GetExtension (file) into grouping
orderby grouping.Key
select grouping
9.6.1.3 根据多个键进行分组
在9.4.1.6 基于多个键的 Join中我们使用了匿名类型辅助连接,GroupBy 中也可以通过匿名类型辅助分组:
from n in new[] { "Tom", "Dick", "Harry", "Mary", "Jay" }
group n by new
{
FirstLetter = n[0],
Length = n.Length
}
Eureka
在查询表达式中,
group ... by ... 允许我们定义一个键选择器,这个键选择器可以是一个属性、一个方法调用或任何返回一个值的表达式,类似的有:var groupedByAgeRange = from person in people
group person by (person.Age < 30 ? "Below 30" :
person.Age <= 40 ? "Between 30 and 40" : "Above 40") into ageGroup
select new { AgeRange = ageGroup.Key, Members = ageGroup };
上述代码
group 代码将比较返回的字符串是否相同
9.6.1.4 自定义相等比较器
GroupBy 的本地查询可以传入相等比较器,方法签名如下:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>
(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
这种方式并不常用,一般会更改 keySelector 委托(表达式):
group name by name.ToUpper()
9.7 集合运算符
| 方法 | 描述 | 等价 SQL 语句 |
|---|---|---|
Concat |
拼接两个序列 | UNION ALL |
Union |
并 集 | UNION |
Intersect |
交 集 | WHERE ... IN |
Except |
差 集 | EXCEPT 或 WHERE ... NOT IN |
9.7.1 Concat 和 Union
Concat 将两个集合拼接; Union 与 Concat 类似,但是会去掉重复元素。如下代码分别输出 { 1, 2, 3, 3, 4, 4, 5 } 和 { 1, 2, 3, 4, 5 } :
int[] seq1 = { 1, 2, 3 }, seq2 = { 3, 4, 4, 5 };
seq1.Concat (seq2).Dump ("Concat");
seq1.Union (seq2).Dump ("Union");
两个有共同基类的 IEnumerable 实例,可以指定泛型参数类型后进行拼接:
MethodInfo[] methods = typeof (string).GetMethods();
PropertyInfo[] props = typeof (string).GetProperties();
IEnumerable<MemberInfo> both = methods.Concat<MemberInfo> (props);
9.7.2 Intersect 和 Except
Intersect 返回两个集合的交集, Except 返回相较第 二 个集合的差集:
int[] seq1 = { 1, 2, 3 }, seq2 = { 3, 4, 5 };
seq1.Intersect (seq2).Dump ("Intersect"); // { 3 }
seq1.Except (seq2).Dump ("seq1.Except (seq2)"); // { 1, 2 }
seq2.Except (seq1).Dump ("seq2.Except (seq1)"); // { 4, 5 }
Except 内部将第一个集合的元素加载至字典,并移除字典中在第二个集合中出现的元素。
9.8 转换方法
| 方法 | 描述 |
|---|---|
OfType |
将 IEnumerable 转换为 IEnumerable<T>,如果转换过程中有错误类型的元素,则丢弃该元素 |
Cast |
将 IEnumerable 转换为 IEnumerable<T>,如果转换过程中有错误类型的元素,则抛出异常 |
ToArray |
将 IEnumerable<T> 转换为 T[] |
ToList |
将 IEnumerable<T> 转换为 List<T> |
ToDictionary |
将 IEnumerable<T> 转换为 Dictionary<TKey,TValue> |
ToLookup |
将 IEnumerable<T> 转换为 ILookup<TKey,TElement> |
AsEnumerable |
将集合类型转换为 IEnumerable<T> |
AsQueryable |
将集合类型转换或转化为 IQueryable<T> |
9.8.1 OfType 和 Cast
OfType 和 Cast 接受非泛型的 IEnumerable 集合并生成泛型的 IEnumerable<T> 序列,进而执行后续的子查询。
ArrayList classicList = new ArrayList();
classicList.AddRange ( new int[] { 3, 4, 5 } );
IEnumerable<int> sequence1 = classicList.Cast<int>();
对于不兼容的类型, OfType 会忽略,继续处理后续元素; Cast 会抛出异常:
// 数据
ArrayList classicList = new ArrayList();
classicList.AddRange ( new int[] { 3, 4, 5 } );
classicList.Add (DateTime.Now);
IEnumerable<int>
ofTypeSequence = classicList.OfType<int>(),
castSequence = classicList.Cast<int>();
ofTypeSequence.Dump ("不兼容的“DateTime”将不会输出");
try {
castSequence.Dump();
}
catch (InvalidCastException ex) {
ex.Message.Dump ("因包含不兼容的元素,转化失败");
}
元素兼容规则
OfType 的转换遵循 C# 的 is 运算符规则,仅允许 引用 转换和 拆箱 转换。
OfType 的内部实现如下:
public static IEnumerable<TSource> OfType <TSource> (IEnumerable source)
{
foreach (object element in source)
if (element is TSource)
yield return (TSource)element;
}
Cast 的内部实现相似,但忽略了兼容性检测:
public static IEnumerable<TSource> Cast <TSource> (IEnumerable source)
{
foreach (object element in source)
yield return (Tsource)element;
}
Eureka
从上述代码可知,
OfType 和Cast 是IEnumerable 的扩展方法(而非IEnumerable<T>),值类型参数也会被装箱,因此它们无法执行 自定义 转换和 数值 转换。此外,装箱和拆箱、3.9.11 类型参数的转换也提到,泛型变量只支持引用转换,不支持装箱转换。
以代码为例:
int[] integers = {1,2,3};
IEnumerable<long> test1 = integers.OfType<long>();
IEnumerable<long> test2 = integers.Cast<long>();
test2 将抛出异常,原因如下:C7.0 核心技术指南 第7版.pdf - p483 - C7.0 核心技术指南 第 7 版-P483-20240413210650-whsmvj7
Cast 查询语法
Cast 的查询语法通过在 from 语句中定义类型实现,用法如下:
object[] untyped = { 1, 2, 3 };
var query1 =
from i in untyped.Cast<int>() // Without syntactic shortcut
select i * 10;
object[] untyped = { 1, 2, 3 };
var query2 =
from int i in untyped // Notice we've slipped in "int"
select i * 10;
9.8.2 ToArray、ToList、ToDictionary 和 ToLookup
ToDictionary 和 ToLookup 不同点在于, ToDictionary 不允许元素的 Key 重复,而 ToLookup 可以:
string[] names = { "Tom", "Dick", "Harry", "Marry", "Jay" };
names.ToLookup(n => n.Length).Dump(); // 正常
names.ToDictionary(n => n.Length).Dump(); // 运行时报错
Eureka
ToLookup 和 GroupBy 相同,唯一的区别是 GroupBy 可以延迟执行。
9.9 元素运算符
| 方法 | 描述 | 等价 SQL |
|---|---|---|
First FirstOrDefault |
返回序列中的第一个元素,可添加可选的断言。 | SELECT TOP 1 ... ORDER BY ... |
Last LastOrDefault |
返回序列中的最后一个元素,可添加可选的断言。 | SELECT TOP 1 ... ORDER BY ... DESC |
Single SingleOrDefault |
和 First/FirstOrDefault 几乎等价,但是在多于一个匹配时抛出异常 |
|
ElementAt ElementAtOrDefault |
返回特定位置的元素 | 抛出异常 |
DefaultIfEmpty |
当序列没有任何元素时,返回一个单元素序列,且元素值为 default(TSource) |
OUTER JOIN |
default(TSource) 对于引用类型元素为 null,对于bool 类型为false,而对于数值类型为零。
9.9.1 First、Last 和 Single
Single 运算符要求集合中有且仅有一个匹配元素; SingleOrDefault 则允许集合中有一个或者零个匹配元素,如下代码运行结果分别是:
int[] numbers = { 1, 2, 3, 4, 5 };
numbers.Single(n => n % 3 == 0);
numbers.Single(n => n % 2 == 0);
numbers.Single(n => n > 10);
numbers.SingleOrDefault(n => n > 10);
numbers.SingleOrDefault(n => n % 2 == 0);
- 3
- 错误
- 错误
- 0
- 错误
在 L2S 和 EF 中,
Single 运算符通常用在使用主键从数据库中检索一行数据的场景:Customer cust = Customers.Single (c => c.ID == 3);
9.9.2 ElementAt 运算符
Enumerable.ElementAt 在输入序列恰好实现了 IList<T> 的情况下会直接调用 IList<T> 的索引器。否则它将枚举 n 次,并返回下一个元素。
L2S 和 EF 不支持
ElementAt 运算符。
9.9.3 DefaultIfEmpty
DefaultIfEmpty 在输入序列为空序列(而非 null)时返回只包含一个 default(TSource) 元素的序列,否则返回原始的输入序列。
它主要用来实现展平结果集的外连接。请参见 9.3.2.8 节和 9.4.1.9 节。
9.10 聚合方法
| 方法 | 描述 | 等价 SQL |
|---|---|---|
Count LongCount |
返回输入序列中元素的个数,可以添加可选的断言 | COUNT(...) |
Min Max |
返回序列中的最小和最大的元素 | MIN(...)MAX(...) |
Sum Average |
计算序列中元素的和或平均数 | SUM(...)AVG(...) |
Aggregate |
执行自定义聚合方法 | 异常抛出 |
9.10.2 Min 和 Max
输入的序列若未实现 IComparable<T> 接口,则需要提供 selector 表达式。
9.10.3 Sum 和 Average
Sum 和 Average 对类型的要求比较严格,序列元素必须是数值类型。对于 Average ,其返回值只能是 decimal、float 或 double。可以参考下表:
C7.0 核心技术指南 第7版.pdf - p487 - C7.0 核心技术指南 第 7 版-P487-20240414144821-5zw4gan
Average 会将输入值隐式向上转换以避免精度损失。
9.10.4 Aggregate
Aggregate 用于实现自定义聚合算法,如下例子实现了 Sum 的功能:
int[] numbers = { 1, 2, 3 };
numbers.Aggregate (0, (seed, n) => seed + n).Dump();
Aggregate 有三个参数:
- 种子,指定积累结果的 初始 值;
-
Func<>,用于自定义 聚合算法 ; - 映射最终值,可选参数。
该运算符在 L2S 和 EF 中无法使用。
C7.0 核心技术指南 第7版.pdf - p488 - C7.0 核心技术指南 第 7 版-P488-20240414154118-zhicuhk
9.10.4.1 无种子的聚合操作
在使用 Aggregate 运算符时可以省略种子。编译器会自动将集合的第一个元素隐式地作为种子。例如,如下代码运行额结果是 0 6 :
int[] numbers = { 1, 2, 3 };
numbers.Aggregate(0, (prod, n) => prod * n).Dump();
numbers.Aggregate((prod, n) => prod * n).Dump();
9.10.4.2 无种子 Aggregate 的陷阱
无种子聚合方法要求传入的委托满足交换性和结合性,否则计算结果将出现不一致。例如如下代码计算结果分别为 27 29 :
// (total, n) => total + n * n
int[] numbers={2, 3, 4};
numbers.Aggregate ((total, n) => total + n * n).Dump();
numbers.Aggregate (0, (total, n) => total + n * n).Dump();
此外,LINQ to Object 和 PLINQ 的计算逻辑也有不同。对于 f(total, n) => total + n * n:
// LINQ to Object 的计算方式
f(f(f(0, 2), 3), 4)
// PLINQ 的计算方式
f(f(0, 2), f(3, 4))
C7.0 核心技术指南 第7版.pdf - p490 - C7.0 核心技术指南 第 7 版-P490-20240414155721-3kv2xn6
9.11 量词运算符
| 方法 | 描述 | 等价 SQL |
|---|---|---|
Contains |
如果给定序列中包含指定元素则返回 true | WHERE ... IN (...) |
Any |
如果序列中有任何元素满足给定的断言则返回 true | WHERE ... IN (...) |
All |
如果序列中所有元素都满足给定断言则返回 true | WHERE (...) |
SequenceEqual |
如果第二序列和输入序列包含的元素相同则返回 true |
9.11.1 Contains 和 Any
Contains 方法接受一个 TSource 类型的参数;而 Any 则接受一个可选的断言。Any 包含了 Contains 的所有功能。
Tips
Any 常用在数据库查询的子查询(即嵌套查询)中,例如:from c in dataContext.Customers
where c.Purchases.Any (p => p.Price > 10_000)
select c
9.11.2 All 和 SequenceEqual
All 运算符,当序列中的所有元素都满足断言时,返回 true。
Customers.Where (c => c.Purchases.All (p => p.Price < 100))
SequenceEqual 运算符比较两个序列。当两个序列都含有相同的元素,且顺序也相同,SequenceEqual 返回 true。如下代码运行结果为 true :
var query = "Hello".Distinct();
query.SequenceEqual("Helo").Dump();
SequenceEqual 支持可选的相等比较器,默认的比较器为 EqualityComparer<T>.Default 。
9.12 生成集合的方法
| 方法 | 描述 |
|---|---|
Empty |
创建一个空序列 |
Repeat |
创建一个含有重复元素的序列 |
Range |
创建一个整数序列 |
Empty、Repeat 和 Range 都是静态的非扩展方法,它们只能创建简单的本地序列。
9.12.1 Empty
Empty 仅接收一个类型参数,该运算符将创建一个 空序列 。它可以和 ?? 运算符配合使用,防止 null 元素。以如下代码为例,第 一 组代码将抛出异常:
// 数据
int[][] numbers =
{
new int[] { 1, 2, 3 },
new int[] { 4, 5, 6 },
null
};
IEnumerable<int> flat = numbers.
SelectMany (innerArray => innerArray).Dump();
IEnumerable<int> flat = numbers
.SelectMany (innerArray => innerArray ?? Enumerable.Empty<int>()).Dump();
Question
试比较
Empty 和DefaultIfEmpty。9.9.3 DefaultIfEmpty
第9章 LINQ 运算符的更多相关文章
- C#图解教程 第十九章 LINQ
LINQ 什么是LINQLINQ提供程序 匿名类型 方法语法和查询语法查询变量查询表达式的结构 from子句join子句什么是联结查询主体中的from-let-where片段 from子句let子句w ...
- C Primer Plus_第5章_运算符、表达式和语句_编程练习
Practice 1. 输入分钟输出对应的小时和分钟. #include #define MIN_PER_H 60 int main(void) { int mins, hours, minutes; ...
- python 教程 第三章、 运算符与表达式
第三章. 运算符与表达式 1) 运算符 + 加 - 减 * 乘 ** 幂 / 除 // 取整除 % 取模 << 左移 >> 右移 & 按位与 | 按位或 ^ 按位 ...
- 第04章_MySQL运算符详解
第04章_运算符 1. 算术运算符 算术运算符主要用于数学运算,其可以连接运算符前后的两个数值或表达式,对数值或表达式进行加(+).减(-).乘(*).除(/)和取模(%)运算. 1.加法与减法运算符 ...
- 第5章 LINQ
5.4 LINQ查询运算符 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- C#高级编程9 第11章 Linq
Linq 1.Linq概述 列表和实体 准备数据: public class Championship { public int Year { get; set; } public string Fi ...
- C#高级编程 (第六版) 学习 第六章:运算符和类型强制转换
第六章 运算符和类型强制转换 1,运算符 类别 运算符 算术运算符 + - * / % 逻辑运算符 & | ^ ~ && || ! 字符串连接运算符 + 增量和减量运算符 ++ ...
- C#高级编程(第9版) 第11章 LINQ 笔记
概述语言集成查询(Language Integrated Query, LINQ)在C#编程语言中集成了查询语法,可以用相同的语法访问不同的数据源.LINQ提供了不同数据源的抽象层,所以可以使用相同的 ...
- 《精通C#》第十二章 Linq
Linq是在.Net3.5之后首次引入的,这种查询语言简单易学,可用范围非常广泛在学着之前,一直用在数据库操作之上,但是在学习这节课之后才发现,凡是实现泛型的接口类型都可以使用linq,简单来说就是实 ...
- Swift中文教程 第2章 基本运算符 (20140604更新)
今天我在下午又进行了新的翻译,之前我翻译的大家有没有看啊,感觉如何,我昨天下午不小心点了什么原先的文章都没了,大家的评论也没了,现在我也找不回来了,希望大家能继续给我新的评论和支持点个推荐,毕竟现在学 ...
随机推荐
- 关于switch的优化:转移表(完结)
用转移表来代替功能实现中的 switch 语句 1. switch 语句应用场景 在许多场景中会用到条件判断,此时简单的条件判断一般通过 if/else 语句实现:如果涉及三条以上的功能分支一般会通过 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 14 期(2024年11.18-11.24)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- java——棋牌类游戏五子棋(webwzq1.0)之一(基础)
这是本人最近一段时间写的斗地主的java代码,大体框架都实现了,一些细节还需要后续优化. package basegame; import java.awt.Button; import java.a ...
- Git之清除历史记录操作
近期公司需要将之前代码仓库中的提交记录都清理,所以操作一下,记录一下步骤: 安全考虑: 有时候在提交代码时,不小心提交了敏感数据,如账号密码什么的,这样在历史记录中就可以查看到,这样很不安全,所以就需 ...
- tmux之常见问题
1. 使用tmux ls的时候显示错误 failed to connect to server: Connection refused 解决: 查看进程是否存在 ps -aux|grep tmux 发 ...
- Phpstorm之快捷键
常用快捷键 1.ctrl+alt+s 快速打开setting系统设置 2.CTRL+/ 单行注释/取消注释 CTRL+SHIFT+/ 块状注释/取消块状注释 3.方法体上面打入'/**' 再按回车键 ...
- # 为什么要使用 MediatR 的 3 个理由和 1 个不使用它的原因
为什么要使用 MediatR 的 3 个理由和 1 个不使用它的原因 https://codeopinion.com/why-use-mediatr-3-reasons-why-and-1-reaso ...
- go语言签发和验证license
https://www.cnblogs.com/guangdelw/p/18328342 生成非对称密钥 package main import ( "crypto/rand" & ...
- gitlab runner install
gitlab-runner install -d /home/gitlab-runner/ --syslog --user gitlab-runner
- Linux readonly
1. 概念readonly在shell脚本中,用来标记变量是只读的,后续执行过程中就不能对其值进行改变,这个用来定义一些常量类的变量. 如果对其进行重新赋值,会提示错误 [root@localhost ...