繁体   English   中英

了解 LINQ 中的 DefaultIfEmpty

[英]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不是Int32StringChar等构建块类型之一。 如果是,则将应用默认提升,例如,对于字符串,将提供" " (空白字符)。

现在让我们检查提到的 LINQ 语句。

只是为了纪念,除非将聚合运算符或 To{a collection}() 应用于 LINQ 表达式,否则将执行延迟评估(荣誉延迟)。

在此处输入图像描述

下面的图像虽然不属于 C#,但有助于理解它的含义。

鉴于惰性求值,我们现在明智地认识到使用查询表达式的 LINQ 会在请求时进行求值,即按需求值。

在此处输入图像描述

因此,如果满足joinon关键字表示的相等性,则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

在此之后只有cps可用,代表一个类别及其加入的产品。

现在请注意,整个查询与以下形式相同:

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.

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