61

我有一个包含以下两个属性的类:

public int Id      { get; private set; }
public T[] Values  { get; private set; }

我已经做到IEquatable<T>并覆盖了object.Equals这样的:

public override bool Equals(object obj)
{
    return Equals(obj as SimpleTableRow<T>);
}

public bool Equals(SimpleTableRow<T> other)
{
    // Check for null
    if(ReferenceEquals(other, null))
        return false;

    // Check for same reference
    if(ReferenceEquals(this, other))
        return true;

    // Check for same Id and same Values
    return Id == other.Id && Values.SequenceEqual(other.Values);
}

当有覆盖时,object.EqualsGetHashCode当然也必须覆盖。但是我应该实现什么代码?如何从通用数组中创建哈希码?以及如何将它与Id整数结合起来?

public override int GetHashCode()
{
    return // What?
}
4

9 回答 9

93

由于这个线程中提出的问题,我发布了另一个回复,显示如果你弄错了会发生什么......主要是你不能使用数组的GetHashCode(); 正确的行为是运行它时不会打印任何警告...切换评论以修复它:

using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
    static void Main()
    {
        // first and second are logically equivalent
        SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
            second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);

        if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
        { // proven Equals, but GetHashCode() disagrees
            Console.WriteLine("We have a problem");
        }
        HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
        set.Add(first);
        set.Add(second);
        // which confuses anything that uses hash algorithms
        if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
    }
}
class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
{

    public SimpleTableRow(int id, params T[] values) {
        this.Id = id;
        this.Values = values;
    }
    public int Id { get; private set; }
    public T[] Values { get; private set; }

    public override int GetHashCode() // wrong
    {
        return Id.GetHashCode() ^ Values.GetHashCode();
    }
    /*
    public override int GetHashCode() // right
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }
    */
    public override bool Equals(object obj)
    {
        return Equals(obj as SimpleTableRow<T>);
    }
    public bool Equals(SimpleTableRow<T> other)
    {
        // Check for null
        if (ReferenceEquals(other, null))
            return false;

        // Check for same reference
        if (ReferenceEquals(this, other))
            return true;

        // Check for same Id and same Values
        return Id == other.Id && Values.SequenceEqual(other.Values);
    }
}
于 2009-03-12T15:16:08.393 回答
39

FWIW,在哈希码中使用值的内容是非常危险的。只有当你能保证它永远不会改变时,你才应该这样做。但是,由于它是暴露的,我认为不能保证它是可能的。对象的哈希码永远不应该改变。否则,它将失去其作为 Hashtable 或 Dictionary 中的键的值。考虑在 Hashtable 中使用对象作为键的难以发现的错误,它的 hashcode 由于外部影响而发生变化,您再也无法在 Hashtable 中找到它!

于 2009-03-12T14:17:16.787 回答
4

由于 hashCode 有点像存储对象的键(就像在哈希表中一样),所以我只使用 Id.GetHashCode()

于 2009-03-12T14:19:25.003 回答
2

怎么样:

    public override int GetHashCode()
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }

这应该与 兼容SequenceEqual,而不是对数组进行参考比较。

于 2009-03-12T14:14:13.847 回答
1
public override int GetHashCode() {
   return Id.GetHashCode() ^ Values.GetHashCode();  
}

评论和其他答案中有几个优点。如果将对象用作字典中的键,则 OP 应考虑是否将值用作“键”的一部分。如果是这样,那么它们应该是哈希码的一部分,否则,不是。

另一方面,我不确定为什么 GetHashCode 方法应该反映 SequenceEqual。它旨在计算哈希表中的索引,而不是完全相等的决定因素。如果使用上面的算法有很多哈希表冲突,并且如果它们的值的顺序不同,那么应该选择一个考虑顺序的算法。如果顺序并不重要,请节省时间并且不要考虑它。

于 2009-03-12T14:12:18.437 回答
1

我只需要添加另一个答案,因为没有提到一个更明显(并且最容易实现)的解决方案 - 不包括您的GetHashCode计算中的集合!

这里似乎忘记的主要事情是结果的唯一性GetHashCode不是必需的(或者在许多情况下甚至是可能的)。不相等的对象不必返回不相等的哈希码,唯一的要求是相等的对象返回相等的哈希码。因此,根据该定义,以下实现GetHashCode对所有对象都是正确的(假设有正确的Equals实现):

public override int GetHashCode() 
{ 
    return 42; 
} 

当然,这会在哈希表查找中产生最差的性能,O(n) 而不是 O(1),但它在功能上仍然是正确的。

GetHashCode考虑到这一点,对于碰巧具有任何类型的集合作为其一个或多个成员的对象,我的一般建议是简单地忽略它们并GetHashCode仅基于其他标量成员进行计算。这将非常有效,除非您将大量对象放入哈希表中,其中所有标量成员都具有相同的值,从而产生相同的哈希码。

尽管散列码值的分布减少,但在计算散列码时忽略集合成员也可以提高性能。请记住,使用哈希码应该通过不需要调用EqualsN 次来提高哈希表的性能,而只需要调用一次 GetHashCode 并快速查找哈希表。如果每个对象都有一个包含 10,000 个项目的内部数组,这些项目都参与哈希码的计算,那么通过良好分布获得的任何好处都可能会丢失。如果生成哈希码的成本要低得多,那么最好使用分布式哈希码。

于 2012-08-21T16:26:14.967 回答
0

我会这样做:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;
于 2009-03-12T14:13:23.037 回答
0

前提是 Id 和 Values 永远不会改变,并且 Values 不为空......

public override int GetHashCode()
{
  return Id ^ Values.GetHashCode();
}

请注意,您的类不是不可变的,因为任何人都可以修改 Values 的内容,因为它是一个数组。鉴于此,我不会尝试使用其内容生成哈希码。

于 2009-03-12T14:14:00.057 回答
0

我知道这个线程很老了,但我写了这个方法来允许我计算多个对象的哈希码。这对这个案例非常有帮助。它并不完美,但它确实满足了我的需求,很可能也满足了你的需求。

我真的不能把它归功于它。我从一些 .net gethashcode 实现中得到了这个概念。我使用的是 419(毕竟,这是我最喜欢的大素数),但你可以选择任何合理的素数(不要太小……不要太大)。

所以,这是我获取哈希码的方法:

using System.Collections.Generic;
using System.Linq;

public static class HashCodeCalculator
{
    public static int CalculateHashCode(params object[] args)
    {
        return args.CalculateHashCode();
    }

    public static int CalculateHashCode(this IEnumerable<object> args)
    {
        if (args == null)
            return new object().GetHashCode();

        unchecked
        {
            return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
        }
    }
}
于 2010-12-18T19:19:16.447 回答