LINQ GroupBy для нескольких полей ссылочного типа;Пользовательский компаратор равенства

StackOverflow https://stackoverflow.com//questions/12703186

Вопрос

Итак, я просмотрел около 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);

Это группы, но дубликаты все еще есть.

  1. Как указать собственный код для сравнения полей 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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top