C# - 类的通用HashCode实现

Arn*_* F. 10 c# hashcode

我正在研究如何为类构建最好的HashCode,我看到了一些算法.我看到了这一个:Hash Code实现,似乎是.NET类HashCode方法类似(通过反映代码看).

所以问题是,为什么不创建上面的静态类以便自动构建HashCode,只需传递我们认为是"键"的字段.

// Old version, see edit
public static class HashCodeBuilder
{
    public static int Hash(params object[] keys)
    {
        if (object.ReferenceEquals(keys, null))
        {
            return 0;
        }

        int num = 42;

        checked
        {
            for (int i = 0, length = keys.Length; i < length; i++)
            {
                num += 37;
                if (object.ReferenceEquals(keys[i], null))
                { }
                else if (keys[i].GetType().IsArray)
                {
                    foreach (var item in (IEnumerable)keys[i])
                    {
                        num += Hash(item);
                    }
                }
                else
                {
                    num += keys[i].GetHashCode();
                }
            }
        }

        return num;
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

// Old version, see edit
public sealed class A : IEquatable<A>
{
    public A()
    { }

    public string Key1 { get; set; }
    public string Key2 { get; set; }
    public string Value { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as A);
    }

    public bool Equals(A other)
    {
        if(object.ReferenceEquals(other, null)) 
            ? false 
            : Key1 == other.Key1 && Key2 == other.Key2;
    }

    public override int GetHashCode()
    {
        return HashCodeBuilder.Hash(Key1, Key2);
    }
}
Run Code Online (Sandbox Code Playgroud)

会更简单,总是自己的方法,不是吗?我错过了什么?


编辑

根据所有评论,我得到以下代码:

public static class HashCodeBuilder
{
    public static int Hash(params object[] args)
    {
        if (args == null)
        {
            return 0;
        }

        int num = 42;

        unchecked
        {
            foreach(var item in args)
            {
                if (ReferenceEquals(item, null))
                { }
                else if (item.GetType().IsArray)
                {
                    foreach (var subItem in (IEnumerable)item)
                    {
                        num = num * 37 + Hash(subItem);
                    }
                }
                else
                {
                    num = num * 37 + item.GetHashCode();
                }
            }
        }

        return num;
    }
}


public sealed class A : IEquatable<A>
{
    public A()
    { }

    public string Key1 { get; set; }
    public string Key2 { get; set; }
    public string Value { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as A);
    }

    public bool Equals(A other)
    {
        if(ReferenceEquals(other, null))
        {
            return false;
        }
        else if(ReferenceEquals(this, other))
        {
            return true;
        }

        return Key1 == other.Key1
            && Key2 == other.Key2;
    }

    public override int GetHashCode()
    {
        return HashCodeBuilder.Hash(Key1, Key2);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 12

你的Equals方法被破坏 - 它假设具有相同哈希码的两个对象必然相等.事实并非如此.

你的哈希码方法看起来很好看,但实际上可以做一些工作 - 见下文.它意味着在任何时候调用任何值类型值创建数组,但除此之外它没关系(正如SLaks指出的那样,集合处理存在一些问题).您可能需要考虑编写一些通用的重载,以避免常见情况下的性能损失(可能是1,2,3或4个参数).您可能还想使用foreach循环而不是普通for循环,只是为了惯用.

你可以做同样的排序为平等的事情,但它是稍硬和混乱.

编辑:对于哈希代码本身,您只需要添加值.我怀疑你是试图做这种事情:

int hash = 17;
hash = hash * 31 + firstValue.GetHashCode();
hash = hash * 31 + secondValue.GetHashCode();
hash = hash * 31 + thirdValue.GetHashCode();
return hash;
Run Code Online (Sandbox Code Playgroud)

但是,乘以 31的哈希,它不会增加 31.目前您的散列码总是返回相同的值相同,无论他们是否是在相同的顺序,这是不理想的.

编辑:似乎对使用的哈希码有一些混淆.我建议任何不确定的人阅读文档Object.GetHashCode,然后阅读Eric Lippert 关于哈希和平等博客文章.

  • @Zidad:哈希码用作第一遍,但不是*only*争用.你确实应该总是在哈希相等之后检查真正的相等性. (3认同)
  • @Arnaud:你的GetHashCode方法应该使用*unchecked*算术,而不是检查. (2认同)