Понимание DefaultIfEmpty в LINQ - PullRequest
       33

Понимание DefaultIfEmpty в LINQ

2 голосов
/ 23 апреля 2020

Я не понимаю, как работает метод DefaultIfEmpty. Обычно он напоминает лево-внешнее объединение в LINQ.

  • DefaultIfEmpty() метод должен быть запущен для коллекции .
  • DefaultIfEmpty() метод не может быть запущен для null ссылки на коллекцию.

Пример кода Я не понимаю некоторых моментов, которые

  • делает p, что после ключевого слова into, ссылаются на 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

Код из 101 Примеры LINQ .

Ответы [ 2 ]

1 голос
/ 23 апреля 2020

Я обычно не отвечаю на свой вопрос, однако, я думаю, что некоторые люди могут посчитать этот вопрос несколько сложным. На первом шаге необходимо выяснить рабочий лог c группы методов 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 результат. Что, если l1.Add(null); прокомментирован? Это в вашем распоряжении, не трудно догадаться вообще.

l2 имеет элемент , который имеет null, так как foo не является одним из типов строительных блоков, таких как Int32, String или Char. Если бы это было, то по умолчанию было бы применено повышение, например, для строки, " " (пустой символ) предоставляется.

Теперь давайте рассмотрим упомянутый оператор LINQ.

Просто для запоминания, если к выражению LINQ не применен оператор агрегирования или To {коллекция} (), выполняется ленивая оценка (отложенная честь).

enter image description here

Следующее изображение, хотя и не принадлежащее C#, помогает понять, что оно означает.

В свете ленивой оценки мы теперь хорошо осознаем этот факт что LINQ с использованием выражения запроса оценивается по запросу, то есть по запросу.

enter image description here

Итак, ps содержит товары, если выполняется равенство, выраженное в on ключевом слове join. Кроме того, ps имеет различные позиции продукта при каждом требовании выражения LINQ. В противном случае, если не используется DefaultIfEmpty(), select не попадет, таким образом, не повторяется и не дает никакого Console.WriteLine($"{productName}: {category}");. (Пожалуйста, поправьте меня, если я ошибаюсь.)

0 голосов
/ 23 апреля 2020

Ответы

Does p refer to products after into keyword?

p в предложении from - это новая локальная переменная, относящаяся к одному продукту одной категории.

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

Да, ps - группа товаров для категории c. Но это не последовательность последовательностей, просто IEnumerable<Product>, точно так же, как c - это отдельная категория, а не все категории в объединении групп.

В запросе вы видите данные только для одного результата строка , никогда не будет результатом объединения всей группы. Посмотрите на окончательный select, он печатает одну категорию и один продукт, к которому он присоединился. Этот продукт относится к группе продуктов ps, к которой присоединилась категория one .

Затем запрос выполняет обход всех категорий и всех групп продуктов.

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 со своим собственным Passengers, используя Cars.SelectMany(car => car.Passengers, (car, passenger) => (car, passenger));

Так в вашем запросе

from group_join_result into ps
from p in ps.DefaultIfEmpty()

создает новое объединение предыдущего результата объединения групп со своими собственными данными (списками сгруппированных продуктов), прошедшими через DefaultIfEmpty с использованием SelectMany.

Заключение

В конце концов, сложность заключается в запросе Linq, а не в методе DefaultIfEmpty. Этот метод просто объясняется на странице MSDN, которую я разместил в комментарии. Он просто превращает коллекцию без элементов в коллекцию, которая имеет 1 элемент, который является либо значением по умолчанию (), либо предоставленным значением.

Скомпилированный источник

Это примерно 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 view.

...