LINQ GroupBy для нескольких полей ссылочного типа;Пользовательский компаратор равенства
-
13-12-2019 - |
Вопрос
Итак, я просмотрел около 20 примеров на SO и в других местах, но не нашел ни одного, который бы отражал то, что я пытаюсь сделать.Этот - Могу ли я указать свой явный встроенный компаратор типов? - похоже на то, что мне нужно, но не заходит достаточно далеко (или я не понимаю, как идти дальше).
- У меня есть список LoadData, объект LoadData имеет поля как ссылочного, так и типов значений.
- Необходимо сгруппировать смесь полей ссылки и значения, спроецировать вывод на анонимный тип.
Необходимо (я думаю) предоставить собственный IEqualityComparer, чтобы указать, как сравнивать поля GroupBy, но они имеют анонимный тип.
private class LoadData { public PeriodEndDto PeriodEnd { get; set; } public ComponentDto Component { get; set; } public string GroupCode { get; set; } public string PortfolioCode { get; set; } }
Лучший запрос GroupBy, который я когда-либо делал:
var distinctLoads = list.GroupBy(
dl => new { PeriodEnd = dl.PeriodEnd,
Component = dl.Component,
GroupCode = dl.GroupCode },
(key, data) => new {PeriodEnd = key.PeriodEnd,
Component = key.Component,
GroupCode = key.GroupCode,
PortfolioList = data.Select(d=>d.PortfolioCode)
.Aggregate((g1, g2) => g1 + "," + g2)},
null);
Это группы, но дубликаты все еще есть.
- Как указать собственный код для сравнения полей GroupBy?Например, компоненты можно сравнивать по Component.Code.
Решение
Проблема здесь в том, что ваш тип ключа анонимен, а это значит, что вы не можете объявить класс, реализующий IEqualityComparer<T>
для этого типа ключа.Хотя это было бы возможный писать компаратор, который сравнивал бы анонимные типы на равенство собственным способом (с помощью универсального метода, делегатов и вывода типов), было бы не очень приятно.
Вероятно, два самых простых варианта:
- Сделайте анонимный тип «просто работающим», переопределив Equals/GetHashCode в
PeriodEndDto
иComponentDto
.Если существует естественное равенство, которое вы хотели бы использовать повсюду, это, вероятно, самый разумный вариант.Я бы рекомендовал реализоватьIEquatable<T>
также - Не используйте анонимный тип для группировки — используйте именованный тип, и тогда вы сможете либо переопределить
GetHashCode
иEquals
на этом, или вы могли бы написать собственный компаратор равенства обычным способом.
РЕДАКТИРОВАТЬ: ProjectionEqualityComparer
на самом деле не сработает.Хотя можно было бы написать что-то подобное - своего рода CompositeEqualityComparer
что позволило вам создать компаратор равенства из нескольких пар «проекция + компаратор».Однако это было бы довольно некрасиво по сравнению с анонимным типом.
Другие советы
РЕДАКТИРОВАТЬ:
Как отмечает Джон Скит, это решение кажется лучше, чем оно есть на самом деле, если вы не слишком много думаете об этом, потому что я забыл реализовать GetHashCode.Необходимость реализации Gethashcode делает этот подход, как говорит Джон в своем ответе: «Не очень приятно». Предположительно, это также объяснение (так называемого «необъяснимого») отсутствия EqualityComparer<T>.Create()
В рамках.Ответ оставлю для справки, так как примеры того, чего не следует делать, тоже могут быть поучительны.
ОРИГИНАЛЬНЫЙ ОТВЕТ:
Вы можете использовать подход, предложенный Comparer<T>.Create
шаблон, представленный в .NET 4.5 (но необъяснимым образом отсутствующий в EqualityComparer<T>
).Для этого создайте DelegateEqualityComparer<T>
сорт:
class DelegateEqualityComparer<T> : EqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityComparison;
private DelegateEqualityComparer(Func<T, T, bool> equalityComparison)
{
if (equalityComparison == null)
throw new ArgumentNullException("equalityComparison");
_equalityComparison = equalityComparison;
}
public override bool Equals(T x, T y)
{
return _equalityComparison(x, y);
}
public static DelegateEqualityComparer<T> Create(
Func<T, T, bool> equalityComparison)
{
return new DelegateEqualityComparer<T>(equalityComparison);
}
}
Затем напишите обертки вокруг методов GroupBy, чтобы принять Func<TKey, TKey, bool>
делегат вместо IEqualityComparer<TKey>
параметр.Эти методы оборачивают делегата в DelegateEqualityComparer<T>
экземпляр и передать его соответствующему методу GroupBy.Пример:
public static class EnumerableExt
{
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TKey, TKey, bool> equalityComparison)
{
return source.GroupBy(
keySelector,
DelegateEqualityComparer<TKey>.Create(equalityComparison);
}
}
Наконец, на месте вызова вы можете использовать что-то вроде этого выражения для equalityComparison
аргумент:
(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
&& a.Component.Code.Equals(b.Component.Code)
&& a.GroupCode.Equals(b.GroupCode)