【问题标题】:LINQ GroupBy on multiple ref-type fields; Custom EqualityComparer多个 ref 类型字段上的 LINQ GroupBy;自定义 EqualityComparer
【发布时间】:2012-10-03 06:44:19
【问题描述】:

所以我在 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 进行比较。

【问题讨论】:

    标签: c# linq group-by iequalitycomparer


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 嗨@Jon - 谢谢,我走了第一条路线,它解决了我的问题,很好很容易。不过,有几个问题——通过覆盖 Equals/GetHashCode,我本质上是在告诉使用这些对象的代码,两个不同的实例基于它们持有的数据是相等的。这是否有任何风险 - 即。 .NET Framework 在任何地方都依赖这些方法来知道两个对象是不同的实例?其次,是否有更多关于如何使用 ProjectionEqualityComparer 的示例?我想更好地了解它的工作原理。
    • 任何检查相等性的地方 - 字典、Contains 调用等。如果有一个自然的相等概念,这可能对所有代码都是一件好事。
    【解决方案2】:

    编辑:

    正如 Jon Skeet 所指出的,这个解决方案似乎比现在更好,如果你不去想太多的话,因为我忘记了实现 GetHashCode。正如 Jon 在他的回答中所说,必须实现 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&lt;TKey, TKey, bool&gt; 委托代替 IEqualityComparer&lt;TKey&gt; 参数。这些方法将委托包装在 DelegateEqualityComparer&lt;T&gt; 实例中,并将其传递给相应的 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)
    

    【讨论】:

    • 你还没有实现GetHashCode,所以还不能编译。
    • @phoog - 谢谢,Jon 的帖子解决了我眼前的问题,但我稍后会对此进行深入研究,以更好地了解 GroupBy。
    • @JonSkeet 请注意,我源自EqualityComparer&lt;T&gt;,而不是IEqualityComparer&lt;T&gt;,因此我未能覆盖虚拟GetHashCode 是逻辑错误,而不是编译错误。感谢您指出!为什么框架甚至允许对 EqualityComparer 进行子类化?当我发布上述内容时,我想知道这一点,但现在我看到它是如何更容易忘记实现 GetHashCode(), 我非常好奇。
    • @phoog: EqualityComparer&lt;T&gt;.GetHashCode(T) 虽然是一个抽象方法 - 所以它应该仍然是一个编译错误。
    • 啊,你说得对(脸红)。我确实在 VS 中编写了示例代码,但我必须在禁用 ReSharper 时完成它。我在答案的开头添加了警告/撤回。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-03
    • 2018-09-12
    • 1970-01-01
    • 2015-09-04
    相关资源
    最近更新 更多