[英]Understanding DefaultIfEmpty in LINQ
I don't understand how DefaultIfEmpty
method works.我不明白
DefaultIfEmpty
方法是如何工作的。 It is usually used to be reminiscent of left-outer join in LINQ.它通常用来让人想起 LINQ 中的左外连接。
DefaultIfEmpty()
method must be run on a collection . DefaultIfEmpty()
方法必须在集合上运行。DefaultIfEmpty()
method cannot be run on null
collection reference. DefaultIfEmpty()
方法不能在null
集合引用上运行。 A code example I don't understand some points that一个我不明白的代码示例
p
, which is after into
keyword, refer to products
?into
关键字之后的p
是指products
吗?ps
the group of product objects? ps
是产品对象组吗? I mean a sequence of sequences.DefaultIfEmpty()
isn't used, doesn't p , from p in ps.DefaultIfEmpty()
, run into select
?DefaultIfEmpty()
,从p from p in ps.DefaultIfEmpty()
遇到select
? Why? , ,
#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
Code from 101 Examples of LINQ .代码来自LINQ 的 101 个示例。
I ain't generally answer my own question, however, I think some people might find the question somewhat intricate.我一般不会回答我自己的问题,但是,我认为有些人可能会觉得这个问题有些复杂。 In the first step, the working logic of the
DefaultIfEmpty
method group should be figured out(LINQ doesn't support its overloaded versions, by the by).第一步,应该弄清楚
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");
When being run, seeing that it gives null entered l2 out
result out.运行时,看到它给了
null entered l2 out
result out。 What if l1.Add(null);
如果
l1.Add(null);
is commented in?被评论了? It is at your disposal, not hard to guess at all.
您可以随意使用它,一点也不难猜。
l2
has an item which is of null
since foo
is not one of the building block types like Int32
, String
, or Char
. l2
有一个项目是null
因为foo
不是Int32
、 String
或Char
等构建块类型之一。 If it were, default promotion would be applied to, eg for string, " "
(blank character) is supplied to.如果是,则将应用默认提升,例如,对于字符串,将提供
" "
(空白字符)。
Now let's examine the LINQ statement being mentioned.现在让我们检查提到的 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.
只是为了纪念,除非将聚合运算符或 To{a collection}() 应用于 LINQ 表达式,否则将执行延迟评估(荣誉延迟)。
The followed image, albeit not belonging to C#, helps to get what it means.下面的图像虽然不属于 C#,但有助于理解它的含义。
In the light of the lazy evaluation, we are now wisely cognizant of the fact that the LINQ using query expression is evaluated when requested, that is, on-demand.鉴于惰性求值,我们现在明智地认识到使用查询表达式的 LINQ 会在请求时进行求值,即按需求值。
So, ps
contains product items iff the equality expressed at on
keyword of join
is satisfied.因此,如果满足
join
的on
关键字表示的相等性,则ps
包含产品项。 Further, ps
has different product items at each demand of the LINQ expression.此外,
ps
在 LINQ 表达式的每个需求下都有不同的产品项。 Otherwise, unless DefaultIfEmpty()
is used, select
is not hit thereby not iterating over and not yielding any Console.WriteLine($"{productName}: {category}");
否则,除非使用
DefaultIfEmpty()
,否则不会命中select
从而不会迭代并且不会产生任何Console.WriteLine($"{productName}: {category}");
. . (Please correct me at this point if I'm wrong.)
(如果我错了,请在这一点上纠正我。)
Does p refer to products after into keyword?
The p in the from
clause is a new local variable referring to a single product of one category. from
子句中的 p 是一个新的局部变量,指的是一个类别的单个产品。
Is ps the group of product objects? I mean a sequence of sequences.
Yes, ps
is the group of products for the category c
.是的,
ps
是类别c
的产品组。 But it is not a sequence of sequences, just a simple IEnumerable<Product>
, just like c
is a single category, not all categories in the group join.但它不是一个序列的序列,只是一个简单的
IEnumerable<Product>
,就像c
是一个单一的类别,不是所有的类别在组中加入。
In the query you only see data for one result row , never the whole group join result.在查询中,您只能看到一个结果行的数据,而不是整个组的连接结果。 Look at the final
select
, it prints one category and one product it joined with.看看最终的
select
,它打印了一个类别和一个它加入的产品。 That product comes from the ps
group of product that one category joined with.该产品来自一个类别加入的
ps
产品组。
The query then does the walking over all categories and all their groups of products.然后,查询会遍历所有类别及其所有产品组。
If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?
It is not equal to a Select
, because the from
clause creates a new join with itself, which turns into SelectMany
.它不等于
Select
,因为from
子句与自身创建了一个新的连接,它变成了SelectMany
。
Taking the query by parts, first the group join:分部分查询,首先加入组:
from c in categories
join p in products on c equals p.Category into ps
After this only c
and ps
are usable, representing a category and its joined products.在此之后只有
c
和ps
可用,代表一个类别及其加入的产品。
Now note that the whole query is in the same form as:现在请注意,整个查询与以下形式相同:
from car in Cars
from passenger in car.Passengers
select (car, passenger)
Which joins Cars
with its own Passengers
using Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));
它使用
Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));
将Cars
与其自己的Passengers
连接起来
So in your query所以在你的查询中
from group_join_result into ps
from p in ps.DefaultIfEmpty()
creates a new join of the previous group join result with its own data (lists of grouped products) ran through DefaultIfEmpty using SelectMany.使用 SelectMany 通过 DefaultIfEmpty 使用其自己的数据(分组产品列表)创建先前组连接结果的新连接。
In the end the complexity is in the Linq query and not the DefaultIfEmpty method.最后,复杂性在于 Linq 查询而不是 DefaultIfEmpty 方法。 The method is simply explained on the MSDN page i posted in comment.
我在评论中发布的 MSDN 页面上简单地解释了该方法。 It simply turns a collection with no elements into collection that has 1 element, which is either the default() value or the supplied value.
它只是将没有元素的集合转换为具有 1 个元素的集合,该元素是 default() 值或提供的值。
This is approximately the C# code the query gets compiled to:这大约是查询编译为的 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)
);
Done with the help of ILSpy using C# 8.0 view.在 ILSpy 的帮助下使用 C# 8.0 视图完成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.