LINQ GroupBy on multiple ref-type fields; Custom EqualityComparer

≡放荡痞女 提交于 2019-12-02 00:04:57

The problem here is that your key type is anonymous, which means you can't declare a class that implements IEqualityComparer<T> for that key type. While it would be possible to write a comparator which compared anonymous types for equality in a custom manner (via a generic method, delegates and type inference), it wouldn't be terribly pleasant.

The two simplest options are probably:

  • Make the anonymous type "just work" by overriding Equals/GetHashCode in PeriodEndDto and ComponentDto. If there's a natural equality you'd want to use everywhere, this is probably the sanest option. I'd recommend implementing IEquatable<T> as well
  • Don't use an anonymous type for grouping - use a named type, and then you can either override GetHashCode and Equals on that, or you could write a custom equality comparer in the normal way.

EDIT: ProjectionEqualityComparer wouldn't really work. It would be feasible to write something similar though - a sort of CompositeEqualityComparer which allowed you create an equality comparer from several "projection + comparer" pairs. It would be pretty ugly compared with the anonymous type though.

EDIT:

As Jon Skeet points out, this solution seems better than it is, if you don't think too hard about it, because I have forgotten to implement GetHashCode. Having to implement GetHashCode makes this approach, as Jon says in his answer, "not terribly pleasant." Presumably, this is also the explanation for the (so-called "inexplicable") absence of EqualityComparer<T>.Create() in the framework. I'll leave the answer for reference, as examples of what not to do, can be instructive as well.

ORIGINAL ANSWER:

You could use the approach suggested by the Comparer<T>.Create pattern introduced in .NET 4.5 (but inexplicably absent in EqualityComparer<T>). To do so, create a DelegateEqualityComparer<T> class:

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);
    }
}

Then write wrappers around the GroupBy methods to accept a Func<TKey, TKey, bool> delegate in place of the IEqualityComparer<TKey> parameter. These methods wrap the delegate in a DelegateEqualityComparer<T> instance, and pass that on to the corresponding GroupBy method. Example:

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);
    }
}

Finally, at your call site, you would use something like this expression for the equalityComparison argument:

(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
    && a.Component.Code.Equals(b.Component.Code)
    && a.GroupCode.Equals(b.GroupCode)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!