Простая группировка .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 г.