Простая группировка .NET с использованием LINQ

Давайте поговорим о том, как GroupBy метод LINQ упрощает группировку данных на C #. Мы начнем с базового, а затем рассмотрим все доступные перегрузки метода GroupBy, чтобы изучить расширенные варианты использования.

GroupBy позволяет быстро группировать коллекции связанных данных по определенным свойствам ваших данных. Сгруппированные данные затем упорядочиваются по подгруппам элементов в этих группах.

Примечание. LINQ предоставляет варианты каждого метода в этой статье, которые работают с IEnumerable или IQueryable. В остальном эти методы идентичны, поэтому для целей этой статьи мы проигнорируем различие в типах источников данных.

Простая группировка

Давайте посмотрим на образец из небольшого набора данных о книгах:

Предполагая, что у меня есть эти данные, загруженные в коллекцию IEnumerable<Book> под названием books, я могу затем использовать GroupBy для группировки элементов по различным свойствам, указав ключевой селектор.

Например, я сгруппирую по автору с помощью books.GroupBy(b => b.Author)

Это приводит к IEnumerable<IGrouping<string, Book>>. Не позволяйте этому возвращаемому типу пугать вас - это просто набор групп, каждая из которых основана на строковом значении (независимо от значения, возвращаемого вашим селектором ключей) и содержит Books объектов.

Если это невнятно, давайте посмотрим на следующее:

По сути, мы разбиваем нашу коллекцию на несколько подколлекций, и каждая из них имеет свойство Key любого типа, по которому мы сгруппированы, и сама служит IEnumerable<T>, позволяющим нам перечислять элементы в группе.

Селекторы элементов GroupBy

Если вы не хотите, чтобы все тело сгруппированного элемента во вложенной коллекции, вы можете использовать перегрузку, которая также принимает селектор элементов. Селектор элемента - это просто функция, которая выбирает часть объекта, которую будет содержать сгруппированный элемент.

Например, если бы в нашем предыдущем примере я сделал books.GroupBy(b => b.Author, b.Title), я бы получил набор групп по автору, который затем содержал бы только строковые значения для заголовков этого автора:

По общему признанию, результаты здесь выглядят немного странно при сериализации в JSON. Это связано с тем, что ключ группы не включен в сериализованные результаты, и поэтому мы не видим в списке автора. Будьте уверены, что вы все еще можете добраться до автора, просмотрев свойство Key каждой группы.

Селекторы результатов

Теперь, когда мы узнали, как работать с селекторами ключей и значений, давайте познакомимся с третьим типом селекторов: селекторами результатов.

Селекторы результатов позволяют настраивать созданную коллекцию. Вместо того, чтобы работать с IGrouping<TKey, TValue>, вы можете эффективно проецировать коллекцию в любую форму, в которой вы хотите, чтобы она была.

Давайте воспользуемся этим, чтобы устранить причуду сериализации, которую мы видели в последнем примере:

Здесь мы выбираем автора и заголовок для ключа и значения, как мы делали раньше, но теперь мы проецируем каждую группу в новый анонимный тип, устанавливая свойство Author для ключа каждой группы и устанавливая коллекцию групп на коллекцию значений заголовков. .

Конечный JSON гораздо более полезен для представления нашей группы:

Я должен указать здесь на важный момент - с этой перегрузкой мы больше не возвращаем IEnumerable<IGrouping<TKey, TValue>>, а скорее IEnumerable<TProjected>, где TProjected - это результат работы нашего селектора результатов.

Устройство сравнения равенства

Последний возможный параметр для GroupBy - это средство сравнения на равенство. Компоненты сравнения используются при определении того, к какой группе принадлежит элемент, и могут быть полезны, если у вас есть данные, которые не группируются должным образом.

Например, предположим, что в ваших данных есть несколько строк с разным регистром для одного и того же автора:

  • Майкл Крайтон
  • Майкл Кричтон
  • Майкл Кричтон

Мы можем передать IEqualityComparer<TKey>, который будет использоваться для сравнения различных значений ключа. Поскольку наш ключ author является строковым значением, нам нужен IEqualityComparer<string>.

К счастью, .NET поставляется с некоторыми из них, встроенными в класс StringComparer. В нашем случае мы будем использовать StringComparer.CurrentCultureIgnoreCase для сравнения наших авторов:

Использование компаратора таким образом игнорирует любые различия в регистрах между записями авторов.

Вам может быть интересно, какое значение ключа используется, если несколько значений могут сравниваться с одной и той же группой. Ответ заключается в том, что LINQ использует первое значение, встречающееся в этой группе, в качестве официального ключа группы. Это означает, что даже если вы используете StringComparer для игнорирования различий в регистрах, вы все равно можете получить значение ключа, которое может не соответствовать идеальному форматированию.

Вот почему я бы посоветовал, если вы думаете об использовании IEqualityComparer, вам также следует подумать об очистке и нормализации источника данных.

Тем не менее, если вам нужно сгруппировать элементы по некоторым критериям, отличным от ссылочного равенства, реализация пользовательского IEqualtyComparer может быть подходящим вариантом. Однако я ожидал, что таких случаев будет немного и они будут редкостью.

Заключительные мысли

Перед написанием этой статьи я обнаружил, что результат IGrouping слишком неудобен для работы и повторения и в значительной степени избегал синтаксиса LINQ GroupBy.

После полного изучения этого метода и его перегрузок, я думаю, есть ряд веских причин для использования GroupBy, особенно вариант, который позволяет вам проецировать группы в пользовательские объекты и форматы.

Если у вас все еще есть вопросы или вы хотите узнать больше о рассматриваемом материале, ознакомьтесь с документацией MSDN по методу GroupBy.

Независимо от того, присоединяется ли GroupBy к вашему набору инструментов, которые вы часто используете, или нет, это мощный и способный компонент LINQ, о котором следует помнить.

Первоначально опубликовано на https://killalldefects.com 22 января 2020 г.