在IEnumerable<T>上使用自定义IEqualityComparer进行C# Distinct

53
这是我想要做的事情。我正在使用LINQ to XML查询一个XML文件,它给了我一个IEnumerable<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() 来查看可能的问题。
我在互联网上找到了这方面的例子,但似乎无法使我的实现工作。
希望这里的某个人能看出哪里可能有错! 提前致谢!

你的catch/throw结构使得调用函数不能再选择捕获(ArgumentException)或捕获(IOException)(例如)。对于这种情况,最好完全删除try/catch - 此外,方法的名称将成为异常StackTrace属性的一部分。 - Sam Harwell
2个回答

73

问题出在你的GetHashCode方法上。你应该修改它,让它返回AllianceName的哈希码。

int IEqualityComparer<Village>.GetHashCode(Village obj)
{
    return obj.AllianceName.GetHashCode();
}

问题是,如果Equals返回true,那么具有相同AllianceName的不同Village对象应该具有相同的哈希码,但事实并非如此。由于Distinct在内部构建哈希表来工作,因此你将得到相等的对象,但由于哈希码不同而无法匹配。

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


为什么我们不能重写Distinct的典型相等方法? - Boog
@Boog 当然,你可以在对象本身或单独的相等比较器类中实现这个功能,就像 OP 所希望的那样。自定义相等比较器的用例是当您有不同的方式来考虑对象是否相等(例如对于字符串,区分大小写和不区分大小写的比较)或者由于任何原因无法更改类本身时。在任何情况下,您都应该重写 EqualsGetHashCode 方法,并编写一个适当的 GetHashCode 方法以尊重 Equals 方法。 - Mehrdad Afshari

7

或者更改这行

return alliances.Distinct(new AllianceComparer());

to

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

返回联盟中所有的联盟名称,代码为:return alliances.Select(v => v.AllianceName).Distinct(); 这将返回一个 IEnumerable<string> 而不是 IEnumerable<Village> - superrcat
1
这是一种有趣的替代方法,会让问题的提问者重新思考使用IEqualityComparer的代码。这解决了我的一个问题,所以我很喜欢它。但它并没有回答问题。在我的情况下,我想从键值数据集合中提取唯一的键,并将唯一的键分别存储在列表中。正如@superrcat所提到的,通过这样做,返回的IEnumerable的泛型类型会发生改变。 - Greg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接