简体   繁体   English

了解 LINQ 中的 DefaultIfEmpty

[英]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一个我不明白的代码示例

  • Does p , which is after into keyword, refer to products ?into关键字之后的p是指products吗?
  • Is ps the group of product objects? ps是产品对象组吗? I mean a sequence of sequences.我的意思是一系列序列。
  • If 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不是Int32StringChar等构建块类型之一。 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.因此,如果满足joinon关键字表示的相等性,则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.) (如果我错了,请在这一点上纠正我。)

Answers答案

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

Structure结构

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.在此之后只有cps可用,代表一个类别及其加入的产品。

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 使用其自己的数据(分组产品列表)创建先前组连接结果的新连接。

Conclusion结论

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() 值或提供的值。

Compiled source编译源

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM