C# - 类的通用HashCode实现

时间:2011-03-27 16:52:09

标签: c# hashcode

我正在研究如何为类构建最好的HashCode,我看到了一些算法。我看到了这个:Hash Code implementation,似乎是.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;
    }
}

并像这样使用它:

// 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);
    }
}

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


修改

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

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);
    }
}

3 个答案:

答案 0 :(得分:11)

您的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;

但是将哈希值乘以31,它不会添加 31.目前,对于相同的值,您的哈希码将始终返回相同的值,无论它们是否相同“按照相同的顺序,这是不理想的。

编辑:对于使用的哈希码,似乎存在一些混淆。我建议任何不确定的人都会阅读Object.GetHashCode的文档,然后阅读Eric Lippert的文档blog post about hashing and equality

答案 1 :(得分:2)

这就是我正在使用的:

public static class ObjectExtensions
{
    /// <summary>
    /// Simplifies correctly calculating hash codes based upon
    /// Jon Skeet's answer here
    /// http://stackoverflow.com/a/263416
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="memberThunks">Thunks that return all the members upon which
    /// the hash code should depend.</param>
    /// <returns></returns>
    public static int CalculateHashCode(this object obj, params Func<object>[] memberThunks)
    {
        // Overflow is okay; just wrap around
        unchecked
        {
            int hash = 5;
            foreach (var member in memberThunks)
                hash = hash * 29 + member().GetHashCode();
            return hash;
        }
    }
}

使用示例:

public class Exhibit
{
    public virtual Document Document { get; set; }
    public virtual ExhibitType ExhibitType { get; set; }

    #region System.Object
    public override bool Equals(object obj)
    {
        return Equals(obj as Exhibit);
    }

    public bool Equals(Exhibit other)
    {
        return other != null &&
            Document.Equals(other.Document) &&
            ExhibitType.Equals(other.ExhibitType);
    }

    public override int GetHashCode()
    {
        return this.CalculateHashCode(
            () => Document, 
            () => ExhibitType);
    }
    #endregion
}

答案 2 :(得分:1)

您应该尝试将其投放到keys[i].GetType().IsArray(使用IEnumerable关键字),而不是调用as

您可以通过使用代表集合注册静态字段列表like I do here来修复Equals方法,而无需重复字段列表。
这也避免了每次调用的数组分配。

但请注意,我的代码不处理集合属性。