C# Distinct on IEnumerable<T> 与自定义 IEqualityComparer

Posted

技术标签:

【中文标题】C# Distinct on IEnumerable<T> 与自定义 IEqualityComparer【英文标题】:C# Distinct on IEnumerable<T> with custom IEqualityComparer 【发布时间】:2010-09-30 18:37:16 【问题描述】:

这就是我想要做的。我正在使用 LINQ to XML 查询一个 XML 文件,这给了我一个 IEnumerable&lt;T> 对象,其中 T 是我的“村庄”类,其中填充了此查询的结果。有些结果是重复的,所以我想对 IEnumerable 对象执行 Distinct(),如下所示:

public IEnumerable<Village> GetAllAlliances()

    try
    
        IEnumerable<Village> alliances =
             from alliance in xmlDoc.Elements("Village")
             where alliance.Element("AllianceName").Value != String.Empty
             orderby alliance.Element("AllianceName").Value
             select new Village
             
                 AllianceName = alliance.Element("AllianceName").Value
             ;

        // TODO: make it work...
        return alliances.Distinct(new AllianceComparer());
    
    catch (Exception ex)
    
        throw new Exception("GetAllAlliances", ex);
    

由于默认比较器不适用于 Village 对象,我实现了一个自定义比较器,如 AllianceComparer 类中所示:

public class AllianceComparer : IEqualityComparer<Village>

    #region IEqualityComparer<Village> Members
    bool IEqualityComparer<Village>.Equals(Village x, Village y)
    
        // Check whether the compared objects reference the same data.
        if (Object.ReferenceEquals(x, y)) 
            return true;

        // Check whether any of the compared objects is null.
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;

        return x.AllianceName == y.AllianceName;
    

    int IEqualityComparer<Village>.GetHashCode(Village obj)
    
        return obj.GetHashCode();
    
    #endregion

Distinct() 方法不起作用,因为无论有没有它,我都有完全相同数量的结果。另一件事,我不知道通常是否可行,但我无法进入 AllianceComparer.Equals() 看看可能是什么问题。 我在 Internet 上找到了这样的示例,但我似乎无法使我的实现工作。

希望这里的人可能会看到这里可能出了什么问题! 提前致谢!

【问题讨论】:

您的 catch/throw 构造使得调用函数无法再选择 catch(ArgumentException) 或 catch(IOException)(示例)。对于这种情况,您最好将 try/catch 全部删除 - 此外,方法的名称将成为异常 StackTrace 属性的一部分。 【参考方案1】:

问题在于您的GetHashCode。您应该更改它以返回 AllianceName 的哈希码。

int IEqualityComparer<Village>.GetHashCode(Village obj)

    return obj.AllianceName.GetHashCode();

问题是,如果Equals 返回true,则对象应该具有相同的哈希码,而对于具有相同AllianceName 的不同Village 对象则不然。由于Distinct 通过在内部构建哈希表来工作,因此您最终会得到由于不同的哈希码而根本不会匹配的相等对象。

同理,比较两个文件,如果两个文件的hash不一样,根本不需要检查文件本身。他们不同。否则,您将继续检查它们是否真的相同。这正是Distinct 使用的哈希表的行为。

【讨论】:

为什么我们不能只覆盖 Distinct 的典型相等方法?? @Boog 当然,您可以根据 OP 的需要在对象本身或单独的相等比较器类中执行此操作。自定义相等比较器的用例是当您有不同的方法来考虑对象是否相等时(例如,字符串的区分大小写和不区分大小写的比较),或者当您出于任何原因无法更改类本身时。在任何情况下,您都应该覆盖EqualsGetHashCode,并针对Equals 方法编写适当的GetHashCode【参考方案2】:

return alliances.Select(v =&gt; v.AllianceName).Distinct();

这将返回 IEnumerable&lt;string&gt; 而不是 IEnumerable&lt;Village&gt;

【讨论】:

【参考方案3】:

或者换行

return alliances.Distinct(new AllianceComparer());

return alliances.Select(v => v.AllianceName).Distinct();

【讨论】:

这是一种有趣的替代方法,它会导致提问者重新考虑使用 IEqualityComparer 的代码。这为我解决了一个问题,这就是我喜欢它的原因。不过,它并没有回答这个问题。就我而言,我想从键值数据集合中提取唯一键,并将唯一键单独存储在列表中。正如@superrcat 提到的那样,返回的 IEnumerable 的泛型类型会通过这样做而改变。

以上是关于C# Distinct on IEnumerable<T> 与自定义 IEqualityComparer的主要内容,如果未能解决你的问题,请参考以下文章

django中的DISTINCT ON [重复]

C# Distinct使用

PostgreSQL 的 distinct on 的理解

在子查询中使用 distinct on

具有不同 ORDER BY 的 PostgreSQL DISTINCT ON

将 SELECT DISTINCT ON 与 OrmLite 一起使用