2

我不明白DefaultIfEmpty方法是如何工作的。它通常用来让人想起 LINQ 中的左外连接。

  • DefaultIfEmpty()方法必须在集合上运行。
  • DefaultIfEmpty()方法不能在null集合引用上运行。

一个我不明白的代码示例

  • 关键字p后面的是否指的是?intoproducts
  • 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 个 LINQ 示例的代码。

4

2 回答 2

1

我一般不会回答我自己的问题,但是,我认为有些人可能会觉得这个问题有些复杂。第一步,DefaultIfEmpty要弄清楚方法组的工作逻辑(LINQ不支持它的重载版本,by by)。

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有一个项目null因为foo它不是诸如 、 或 之类的构建块Int32类型String之一Char。如果是,则将应用默认提升,例如,对于字符串," "提供(空白字符)。

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

只是为了纪念,除非将聚合运算符或 To{a collection}() 应用于 LINQ 表达式,否则将执行惰性求值(尊重延迟)。

在此处输入图像描述

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

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

在此处输入图像描述

因此,如果满足关键字 ofps表示的相等性,则包含产品项目。此外,LINQ 表达式的每个需求都有不同的产品项。否则,除非使用了,否则不会被命中,因此不会迭代并且不会产生任何. (如果我错了,请在这一点上纠正我。)onjoinpsDefaultIfEmpty()selectConsole.WriteLine($"{productName}: {category}");

于 2020-04-23T15:00:15.637 回答
0

答案

Does p refer to products after into keyword?

子句中的 pfrom是一个新的局部变量,指的是一个类别的单个产品。

Is ps the group of product objects? I mean a sequence of sequences.

是的,ps是该类别的产品组c。但它不是一个序列的序列,只是一个简单的IEnumerable<Product>,就像c是一个单一的类别,并不是组中的所有类别都加入。

在查询中,您只能看到一个结果的数据,而不是整个组的连接结果。看看 final select,它打印了一个类别和一个它加入的产品。该产品来自一个类别加入ps的产品组。

然后,查询会遍历所有类别及其所有产品组。

If DefaultIfEmpty() isn't used, doesn't p, from p in ps.DefaultIfEmpty(), run into select? Why?

它不等于 a 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与自己的Passengers使用相结合Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));

所以在你的查询中

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 视图完成。

于 2020-04-23T12:48:00.087 回答