[英]Understanding DefaultIfEmpty in LINQ
我不明白DefaultIfEmpty
方法是如何工作的。 它通常用来让人想起 LINQ 中的左外连接。
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
代码来自LINQ 的 101 个示例。
我一般不会回答我自己的问题,但是,我认为有些人可能会觉得这个问题有些复杂。 第一步,应该弄清楚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
result out。 如果l1.Add(null);
被评论了? 您可以随意使用它,一点也不难猜。
l2
有一个项目是null
因为foo
不是Int32
、 String
或Char
等构建块类型之一。 如果是,则将应用默认提升,例如,对于字符串,将提供" "
(空白字符)。
现在让我们检查提到的 LINQ 语句。
只是为了纪念,除非将聚合运算符或 To{a collection}() 应用于 LINQ 表达式,否则将执行延迟评估(荣誉延迟)。
下面的图像虽然不属于 C#,但有助于理解它的含义。
鉴于惰性求值,我们现在明智地认识到使用查询表达式的 LINQ 会在请求时进行求值,即按需求值。
因此,如果满足join
的on
关键字表示的相等性,则ps
包含产品项。 此外, ps
在 LINQ 表达式的每个需求下都有不同的产品项。 否则,除非使用DefaultIfEmpty()
,否则不会命中select
从而不会迭代并且不会产生任何Console.WriteLine($"{productName}: {category}");
. (如果我错了,请在这一点上纠正我。)
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
的产品组。 但它不是一个序列的序列,只是一个简单的IEnumerable<Product>
,就像c
是一个单一的类别,不是所有的类别在组中加入。
在查询中,您只能看到一个结果行的数据,而不是整个组的连接结果。 看看最终的select
,它打印了一个类别和一个它加入的产品。 该产品来自一个类别加入的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 视图完成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.