GetHashCode重写包含泛型数组的对象

时间:2009-03-12 14:08:07

标签: c# arrays generics hashcode

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

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.Equals时,我当然也必须覆盖GetHashCode。但是我应该实现什么代码?如何从通用数组中创建哈希码?如何将其与Id整数组合?

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

9 个答案:

答案 0 :(得分:81)

由于这个帖子中出现的问题,我发布了另一个回复,显示如果你弄错了会发生什么......主要是你不能使用数组的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);
    }
}

答案 1 :(得分:30)

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

答案 2 :(得分:4)

由于hashCode有点存储对象的密钥(lleeke在哈希表中),我只使用Id.GetHashCode()

答案 3 :(得分: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兼容,而不是在数组上进行参考比较。

答案 4 :(得分:1)

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

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

另一方面,我不确定为什么GetHashCode方法应该镜像SequenceEqual。它意味着计算哈希表的索引,而不是完全相等的决定因素。如果使用上述算法存在许多哈希表冲突,并且如果它们在值的序列中不同,则应选择考虑序列的算法。如果序列无关紧要,请节省时间,不要将其考虑在内。

答案 5 :(得分:1)

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

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

public override int GetHashCode() 
{ 
    return 42; 
} 

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

考虑到这一点,我对一个碰巧拥有任何类型集合作为其一个或多个成员的对象实施GetHashCode时的一般建议是简单地忽略它们并仅基于GetHashCode计算在其他标量成员上。这可以很好地工作,除非你在哈希表中放入大量的对象,其中所有的标量成员具有相同的值,从而产生相同的哈希码。

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

答案 6 :(得分:0)

我会这样做:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;

答案 7 :(得分:0)

如果Id和Values永远不会改变,并且Values不为null ......

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

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

答案 8 :(得分: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());
        }
    }
}