LINQ Group通过多个ref-type字段;自定义EqualityComparer

时间:2012-10-03 06:44:19

标签: c# linq group-by iequalitycomparer

所以我在SO和其他地方看了大约20个例子,但是没有找到一个涵盖我想要做的事情。这个 - Can I specify my explicit type comparator inline? - 看起来像我需要的东西,但是还不够(或者我不明白如何进一步了解。)

  • 我有一个LoadData列表,LoadData对象包含引用和值类型的字段
  • 需要对ref和value字段的混合进行分组,将输出投影为匿名类型
  • 需要(我认为)提供自定义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比较组件。

2 个答案:

答案 0 :(得分:8)

这里的问题是您的密钥类型是匿名的,这意味着您无法声明为该密钥类型实现IEqualityComparer<T>的类。虽然可能可能编写一个比较器,它以自定义方式比较匿名类型的相等性(通过泛型方法,委托和类型推断),但它不会非常令人愉快。

两个最简单的选项可能是:

  • 通过覆盖PeriodEndDtoComponentDto中的Equals / GetHashCode,使匿名类型“正常工作”。如果你想要在任何地方使用自然平等,这可能是最安全的选择。我建议同时实施IEquatable<T>
  • 不要使用匿名类型进行分组 - 使用命名类型,然后您可以覆盖GetHashCodeEquals,或者您可以按正常方式编写自定义相等比较器

编辑:ProjectionEqualityComparer不会真的有用。写一些类似的东西是可行的 - 一种CompositeEqualityComparer允许你从几个“投影+比较器”对创建一个相等比较器。与匿名类型相比,它会非常难看。

答案 1 :(得分:2)

编辑:

正如Jon Skeet所指出的,如果你不太考虑它,这个解决方案似乎比它更好,因为我忘记了实现GetHashCode。正如乔恩在答案中所说,必须实施GetHashCode才能实现这种方法,“不是非常令人愉快。”据推测,这也是对框架中EqualityComparer<T>.Create()缺席(所谓的“莫名其妙”)的解释。我将留下答案供参考,作为不该做的例子,也可以是有益的。

原始回答:

您可以使用.NET 4.5中引入的Comparer<T>.Create模式建议的方法(但在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)