首页 > 其他分享 >第9章 LINQ 运算符

第9章 LINQ 运算符

时间:2024-12-29 23:42:09浏览次数:5  
标签:Dump 元素 IEnumerable LINQ 运算符 numbers 序列

第9章 LINQ 运算符

本章所有例子所使用的 names 数组都是一致的:

string[] names = {"Tom", "Dick", "Harry", "Marry", "Jay" };

9.1 概述

标准查询运算符分三类:

  1. 输入是序列(IEnumerable​),输出是序列(IEnumerable​)(IEnumerable​ → IEnumerable​)
  2. 输入是序列(IEnumerable​),输出是单个元素或标量值(IEnumerable​ → value)
  3. 没有输入,输出是序列(void → IEnumerable​)
mindmap 运算符分类 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
OrderByThenBy 将序列按升序排序 ORDER BY
OrderByDescending
ThenByDescending
将序列按降序排序 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();   // 运行时报错

image

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);
  1. 3
  2. 错误
  3. 错误
  4. 0
  5. 错误

在 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​ 有三个参数:

  1. 种子,指定积累结果的 初始 值;
  2. Func<>​,用于自定义 聚合算法
  3. 映射最终值,可选参数。

该运算符在 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

标签:Dump,元素,IEnumerable,LINQ,运算符,numbers,序列
From: https://www.cnblogs.com/hihaojie/p/18639816/chapter-9-linq-operator-1s6ltf

相关文章

  • 第10章 LINQ to XML
    第10章LINQtoXML10.1架构概述——DOM和LINQtoXML的DOMXML文档可以用一棵对象树完整的表示,这称为“文档对象模型(documentobjectmodel)”LINQtoXML由两部分组成:XMLDOM,简称为X-DOM大约10个查询运算符LINQ也可以用于查询W3C标准的旧DOM,不过X-DOM对L......
  • 运算符
    运算符packageOperator;importjava.sql.SQLOutput;publicclasssimpleOperator{publicstaticvoidmain(String[]args){//Ctrl+D快捷键复制当前行到下一行inta=10;intb=20;intc=30;intd=40;......
  • C语言.基础.运算符(二)
    目录运算符类型运算符的优先级运算符的结合性算术运算符赋值运算符关系运算符逻辑运算符 位运算符C语言内置的位运算符: 位运算符的几种特殊操作:1左移实现乘法2右移实现除法3 不使用中间变量交换两个整型变量的值4判断整数的奇偶性5位操作进行高低位交......
  • 04友元和运算符重载
    04友元和运算符重载一、友元(friend关键字)友元的声明,可以放在Public/private/protected中的任意一个位置,不影响!!!1.1为什么要使用友元呢?C++面向对象的目的之一便是:封装。但是封装在某些特殊场合,不太方便。即:某个类需要实现某种功能,但是这个类自身,因为各种原因,无法自己......
  • 写一个方法把如下字符串按运算符切割成数组`18x2÷9+1-6`
    在前端开发中,你可以使用JavaScript的String.prototype.split()方法和正则表达式来达到这个目的。由于你的字符串中包含多种运算符(x、÷、+、-),你可能需要使用一个稍微复杂的正则表达式来匹配这些运算符,并将字符串分割成数组。下面是一个示例函数,它接受一个字符串作为输入,并返回一......
  • Python运算符
    前言随着人工智能的不断发展,python这门技术也越来越重要,很多人都开启了python学习,本文就介绍了python的基础内容——Python运算符。一、Pathon运算符是什么?Python运算符包含了算数运算符、赋值运算符、比较运算符、逻辑运算符、其他运算符。接下来我们说一下各个运算符的代......
  • 通过重载运算符输出强类型枚举的值
    通过重载运算符输出强类型枚举的值例子摘自《现代C++教程:高速上手C++11141720》对于下面的枚举类型:enumclassNEW_ENUM:unsignedint{a,b,c,d=100,e=100};我们可以通过重载运算符输出枚举的值:template<typenameT>std::ostream&oper......
  • 扩展运算符...
    <!DOCTYPEhtml><htmllang="en"><head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,initial-scale=1.0">  <title>Document</title><......
  • Veilog学习笔记<2>语句运算符
    Veilog语句运算符:(1)算术运算符+:加法-:减法*:乘法/:除法%:取模(求余数)        eg :y=7%2 结果1  y=-7%2 结果-1            y=7%-2 结果1   y=-7%-2 结果-1   注:当进行求余运算时,结果的符号将与被除数(即第一个操作数)的符号相同。*......
  • Java 变量和运算符
    Java变量和运算符1.变量(Variable)1.1何为变量1.2数据类型(DataTypes)1.2.1整型:byte、short、int、long1.2.2浮点类型:float、double1.2.3字符类型:char1.2.4布尔类型:boolean1.3变量的使用1.3.1步骤1:变量的声明1.3.2步骤2:变量的赋值1.4.基本数据类型变......