了解 LINQ 中的 DefaultIfEmpty
Understanding DefaultIfEmpty in LINQ
我不明白 DefaultIfEmpty
方法是如何工作的。它通常让人想起 LINQ 中的 left-outer join。
DefaultIfEmpty()
方法必须是 运行 在 集合 . 上
DefaultIfEmpty()
方法不能在 null
集合引用上 运行。
一个代码示例我不明白的地方
into
关键字后的p
是指products
吗?
ps
是产品对象组吗?我的意思是一系列的序列。
- 如果不用
DefaultIfEmpty()
,p、from p in ps.DefaultIfEmpty()
、运行不就变成select
了吗?为什么?
,
#region left-outer-join
string[] categories = {
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood"
};
List<Product> products = GetProductList();
var q = from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
select (Category: c, ProductName: p == null ? "(No products)" : p.ProductName);
foreach (var v in q)
{
Console.WriteLine($"{v.ProductName}: {v.Category}");
}
#endregion
代码来自 101 Examples of LINQ.
答案
Does p refer to products after into keyword?
from
子句中的p是一个新的局部变量,指的是一个类别的单个产品。
Is ps the group of product objects? I mean a sequence of sequences.
是的,ps
是类别 c
的产品组。但它不是一个sequences的sequences,只是一个简单的IEnumerable<Product>
,就像c
是一个单一的类别,并不是group中的所有类别都join。
在查询中您只能看到一个结果 行 的数据,而不会看到整个组连接结果。查看最后的 select
,它打印 one 类别和 one 产品。该产品来自一个类别加入的ps
产品组。
查询然后遍历所有类别及其所有产品组。
If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?
它不等于 Select
,因为 from
子句创建了一个新的与自身的连接,它变成了 SelectMany
.
结构
分部查询,先加入群:
from c in categories
join p in products on c equals p.Category into ps
此后只有c
和ps
可用,代表一个类别及其加入的产品。
现在请注意,整个 查询的格式与:
from car in Cars
from passenger in car.Passengers
select (car, passenger)
它使用 Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));
将 Cars
与其自己的 Passengers
连接起来
所以在你的查询中
from group_join_result into ps
from p in ps.DefaultIfEmpty()
使用 SelectMany 通过 DefaultIfEmpty 使用自己的数据(分组产品列表)运行 对先前的组连接结果创建新连接。
结论
最后,复杂性在于 Linq 查询而不是 DefaultIfEmpty 方法。我在评论中发布的 MSDN 页面上简单地解释了该方法。它只是将没有元素的集合转换为具有 1 个元素的集合,该元素是 default() 值或提供的值。
编译源码
这大约是查询编译成的 C# 代码:
//Pairs of: (category, the products that joined with the category)
IEnumerable<(string category, IEnumerable<Product> groupedProducts)> groupJoinData = Enumerable.GroupJoin(
categories,
products,
(string c) => c,
(Product p) => p.Category,
(string c, IEnumerable<Product> ps) => (c, ps)
);
//Flattening of the pair collection, calling DefaultIfEmpty on each joined group of products
IEnumerable<(string Category, string ProductName)> q = groupJoinData.SelectMany(
catProdsPair => catProdsPair.groupedProducts.DefaultIfEmpty(),
(catProdsPair, p) => (catProdsPair.category, (p == null) ? "(No products)" : p.ProductName)
);
在 ILSpy 的帮助下使用 C# 8.0 视图完成。
我通常不会回答我自己的问题,但是,我认为有些人可能会觉得这个问题有些复杂。
第一步,弄清楚DefaultIfEmpty
方法组的工作逻辑(LINQ不支持其重载版本,顺便说一句)。
class foo
{
public string Test { get; set; }
}
// list1
var l1 = new List<foo>();
//l1.Add(null); --> try the code too by uncommenting
//list2
var l2 = l1.DefaultIfEmpty();
foreach (var x in l1)
Console.WriteLine((x == null ? "null" : "not null") + " entered l1");
foreach (var x in l2)
Console.WriteLine((x == null ? "null" : "not null") + " entered l2");
当运行时,看到它给出了null entered l2 out
的结果。
如果l1.Add(null);
被注释了怎么办?任你摆布,一点也不难猜。
l2
有一个 item 是 null
因为 foo
不是像 [=17= 这样的积木类型之一]、String
或 Char
。如果是,默认促销将应用于,例如对于字符串," "
(空白字符)提供给。
现在让我们检查一下提到的 LINQ 语句。
Just for a remembrance, unless an aggregate operator or a To{a
collection}() is applied to a LINQ expression, lazy evaluation(honor
deferred) is carried out.
下图虽然不属于 C#,但有助于理解其含义。
根据惰性求值,我们现在明智地认识到使用 查询表达式 的 LINQ 在请求时求值,即按需求值。
因此,ps
包含产品项,前提是满足 join
的 on
关键字表示的相等性。此外,ps
在 LINQ 表达式的每个需求中都有不同的产品项目。否则,除非使用 DefaultIfEmpty()
,否则不会命中 select
,从而不会迭代并且不会产生任何 Console.WriteLine($"{productName}: {category}");
。 (如果我错了,请在这一点上纠正我。)
我不明白 DefaultIfEmpty
方法是如何工作的。它通常让人想起 LINQ 中的 left-outer join。
DefaultIfEmpty()
方法必须是 运行 在 集合 . 上
DefaultIfEmpty()
方法不能在null
集合引用上 运行。
一个代码示例我不明白的地方
into
关键字后的p
是指products
吗?ps
是产品对象组吗?我的意思是一系列的序列。- 如果不用
DefaultIfEmpty()
,p、from p in ps.DefaultIfEmpty()
、运行不就变成select
了吗?为什么?
,
#region left-outer-join
string[] categories = {
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood"
};
List<Product> products = GetProductList();
var q = from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
select (Category: c, ProductName: p == null ? "(No products)" : p.ProductName);
foreach (var v in q)
{
Console.WriteLine($"{v.ProductName}: {v.Category}");
}
#endregion
代码来自 101 Examples of LINQ.
答案
Does p refer to products after into keyword?
from
子句中的p是一个新的局部变量,指的是一个类别的单个产品。
Is ps the group of product objects? I mean a sequence of sequences.
是的,ps
是类别 c
的产品组。但它不是一个sequences的sequences,只是一个简单的IEnumerable<Product>
,就像c
是一个单一的类别,并不是group中的所有类别都join。
在查询中您只能看到一个结果 行 的数据,而不会看到整个组连接结果。查看最后的 select
,它打印 one 类别和 one 产品。该产品来自一个类别加入的ps
产品组。
查询然后遍历所有类别及其所有产品组。
If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?
它不等于 Select
,因为 from
子句创建了一个新的与自身的连接,它变成了 SelectMany
.
结构
分部查询,先加入群:
from c in categories
join p in products on c equals p.Category into ps
此后只有c
和ps
可用,代表一个类别及其加入的产品。
现在请注意,整个 查询的格式与:
from car in Cars
from passenger in car.Passengers
select (car, passenger)
它使用 Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));
Cars
与其自己的 Passengers
连接起来
所以在你的查询中
from group_join_result into ps
from p in ps.DefaultIfEmpty()
使用 SelectMany 通过 DefaultIfEmpty 使用自己的数据(分组产品列表)运行 对先前的组连接结果创建新连接。
结论
最后,复杂性在于 Linq 查询而不是 DefaultIfEmpty 方法。我在评论中发布的 MSDN 页面上简单地解释了该方法。它只是将没有元素的集合转换为具有 1 个元素的集合,该元素是 default() 值或提供的值。
编译源码
这大约是查询编译成的 C# 代码:
//Pairs of: (category, the products that joined with the category)
IEnumerable<(string category, IEnumerable<Product> groupedProducts)> groupJoinData = Enumerable.GroupJoin(
categories,
products,
(string c) => c,
(Product p) => p.Category,
(string c, IEnumerable<Product> ps) => (c, ps)
);
//Flattening of the pair collection, calling DefaultIfEmpty on each joined group of products
IEnumerable<(string Category, string ProductName)> q = groupJoinData.SelectMany(
catProdsPair => catProdsPair.groupedProducts.DefaultIfEmpty(),
(catProdsPair, p) => (catProdsPair.category, (p == null) ? "(No products)" : p.ProductName)
);
在 ILSpy 的帮助下使用 C# 8.0 视图完成。
我通常不会回答我自己的问题,但是,我认为有些人可能会觉得这个问题有些复杂。
第一步,弄清楚DefaultIfEmpty
方法组的工作逻辑(LINQ不支持其重载版本,顺便说一句)。
class foo
{
public string Test { get; set; }
}
// list1
var l1 = new List<foo>();
//l1.Add(null); --> try the code too by uncommenting
//list2
var l2 = l1.DefaultIfEmpty();
foreach (var x in l1)
Console.WriteLine((x == null ? "null" : "not null") + " entered l1");
foreach (var x in l2)
Console.WriteLine((x == null ? "null" : "not null") + " entered l2");
当运行时,看到它给出了null entered l2 out
的结果。
如果l1.Add(null);
被注释了怎么办?任你摆布,一点也不难猜。
l2
有一个 item 是 null
因为 foo
不是像 [=17= 这样的积木类型之一]、String
或 Char
。如果是,默认促销将应用于,例如对于字符串," "
(空白字符)提供给。
现在让我们检查一下提到的 LINQ 语句。
Just for a remembrance, unless an aggregate operator or a To{a collection}() is applied to a LINQ expression, lazy evaluation(honor deferred) is carried out.
下图虽然不属于 C#,但有助于理解其含义。
根据惰性求值,我们现在明智地认识到使用 查询表达式 的 LINQ 在请求时求值,即按需求值。
因此,ps
包含产品项,前提是满足 join
的 on
关键字表示的相等性。此外,ps
在 LINQ 表达式的每个需求中都有不同的产品项目。否则,除非使用 DefaultIfEmpty()
,否则不会命中 select
,从而不会迭代并且不会产生任何 Console.WriteLine($"{productName}: {category}");
。 (如果我错了,请在这一点上纠正我。)