GetHashCode扩展方法

时间:2009-04-18 16:34:31

标签: c# hash hashcode gethashcode

在阅读有关覆盖GetHashCode()的StackOverflow上的所有问题和答案后,我编写了以下扩展方法,以便轻松方便地覆盖GetHashCode()

public static class ObjectExtensions
{
    private const int _seedPrimeNumber = 691;
    private const int _fieldPrimeNumber = 397;
    public static int GetHashCodeFromFields(this object obj, params object[] fields) {
        unchecked { //unchecked to prevent throwing overflow exception
            int hashCode = _seedPrimeNumber;
            for (int i = 0; i < fields.Length; i++)
                if (fields[i] != null)
                    hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();
            return hashCode;
        }
    }
}

(我基本上只重构了有人在那里发布的代码,因为我真的很喜欢它可以普遍使用)

我这样使用:

    public override int GetHashCode() {
        return this.GetHashCodeFromFields(field1, field2, field3);
    }

您是否发现此代码存在任何问题?

9 个答案:

答案 0 :(得分:3)

一段时间我写了一些东西,你可能会解决你的问题...(实际上,它可能会被改进,包括你拥有的种子......)

无论如何,该项目名为Essence(http://essence.codeplex.com/),它使用System.Linq.Expression库生成(基于属性)Equals / GetHashCode / CompareTo / ToString的标准表示,以及能够基于参数列表创建IEqualityComparer和IComparer类。 (我还有一些进一步的想法,但希望在继续进一步深入之前获得一些社区反馈。)

(这意味着它几乎和手写一样快 - 主要的不是CompareTo();因为Linq.Expressions在3.5版本中没有变量的概念 - 因此,当你没有得到匹配时,你必须在底层对象上调用CompareTo()两次。使用Linq.Expressions的DLR扩展来解决这个问题。我想我可以使用emit il,但我不是那个启发当时。)

这是一个非常简单的想法,但我之前没有看到它。

现在问题是,我对抛光它感兴趣(包括为codeproject写一篇文章,记录一些代码等),但如果你觉得它可能会被说服这样做会有兴趣。

(codeplex网站没有可下载的软件包;只需转到源代码并抓住它 - 哦,它是用f#编写的(虽然所有测试代码都在c#中),因为这是我感兴趣的东西。)

无论如何,这是项目中测试的c#示例:

    // --------------------------------------------------------------------
    // USING THE ESSENCE LIBRARY:
    // --------------------------------------------------------------------
    [EssenceClass(UseIn = EssenceFunctions.All)]
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence>
    {
        [Essence(Order=0] public int MyInt           { get; set; }
        [Essence(Order=1] public string MyString     { get; set; }
        [Essence(Order=2] public DateTime MyDateTime { get; set; }

        public override int GetHashCode()                                { return Essence<TestEssence>.GetHashCodeStatic(this); }
    ...
    }

    // --------------------------------------------------------------------
    // EQUIVALENT HAND WRITTEN CODE:
    // --------------------------------------------------------------------
    public class TestManual
    {
        public int MyInt;
        public string MyString;
        public DateTime MyDateTime;

        public override int GetHashCode()
        {
            var x = MyInt.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= (MyString == null) ? 0 : MyString.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= MyDateTime.GetHashCode();
            return x;
        }
    ...
    }

无论如何,这个项目,如果有人认为是值得的,需要抛光,但想法在那里......

答案 1 :(得分:2)

这看起来是一种可靠的方法。

我唯一的建议是,如果你真的关心它的性能,你可能想要为几种常见情况添加通用版本(即可能是1-4个args)。这样,对于那些对象(最有可能是小型的,键式复合对象),您将不会有构建数组以传递给方法,循环,任何通用值的装箱等的开销。调用语法将完全相同,但您将针对该情况运行稍微更优化的代码。当然,在你决定是否值得维护权衡之前,我会对此进行一些性能测试。

这样的事情:

public static int GetHashCodeFromFields<T1,T2,T3,T4>(this object obj, T1 obj1, T2 obj2, T3 obj3, T4 obj4) {
    int hashCode = _seedPrimeNumber;
    if(obj1 != null)
        hashCode *= _fieldPrimeNumber + obj1.GetHashCode();
    if(obj2 != null)
        hashCode *= _fieldPrimeNumber + obj2.GetHashCode();
    if(obj3 != null)
        hashCode *= _fieldPrimeNumber + obj3.GetHashCode();
    if(obj4 != null)
        hashCode *= _fieldPrimeNumber + obj4.GetHashCode();
    return hashCode;
}

答案 2 :(得分:1)

我对我看起来很不错,我只有一个问题:遗憾的是你必须使用object[]来传递值,因为这会将您发送给函数的任何值类型包装起来。我不认为你有很多选择,除非你像其他人建议的那样去创建一些通用的重载。

答案 3 :(得分:0)

根据一般原则,你应该尽可能狭窄地调整unchecked的范围,尽管这里并不重要。除此之外,看起来很好。

答案 4 :(得分:0)

public override int GetHashCode() {
    return this.GetHashCodeFromFields(field1, field2, field3, this);
}

(是的,我很迂腐,但这是我看到的唯一问题)

答案 5 :(得分:0)

更优化:

  1. 创建一个代码生成器,该代码生成器使用反射来查看业务对象字段,并创建一个新的部分类,该类重写GetHashCode()(和Equals())。
  2. 在程序以调试模式启动时运行代码生成器,如果代码已更改,请退出并向开发人员重新编译。
  3. 这样做的好处是:

    • 使用反射,您知道哪些字段是值类型,因此是否需要空值检查。
    • 没有开销 - 没有额外的函数调用,没有列表构造等。如果您要进行大量的字典查找,这很重要。
    • 长期实现(在包含大量字段的类中)隐藏在部分类中,远离重要的业务代码。

    缺点:

    • 如果您没有对GetHashCode()进行大量字典查找/调用,那就太过分了。

答案 6 :(得分:0)

我应该指出,在实现GetHashCode时,您几乎不应该进行分配 (这是关于它的some useful blog个帖子。)

params的工作方式(动态生成新数组)意味着这不是一个好的通用解决方案。您最好使用每个字段的方法调用并将散列状态维护为传递给它们的变量(这样可以更容易地使用更好的散列函数和雪崩)。

答案 7 :(得分:0)

除了使用params object[] fields引起的问题之外,我认为在某些情况下,不使用类型信息也可能是性能问题。假设两个类AB具有相同类型和数量的字段,并实现相同的接口I。现在,如果您将AB个对象放到具有相等字段的Dictionary<I, anything>个对象中,则不同类型的对象将最终位于同一个存储桶中。我可能会插入一些语句,如hashCode ^= GetType().GetHashCode();

Jonathan Rupp接受的答案涉及params数组,但不处理值类型的装箱。因此,如果性能非常重要,我可能会声明GetHashCodeFromFields没有对象而只有int个参数,并且不发送字段本身,而是发送字段的哈希码。即。

public override int GetHashCode() 
{
    return this.GetHashCodeFromFields(field1.GetHashCode(), field2.GetHashCode());
}

答案 8 :(得分:0)

可能出现的一个问题是,当乘法次数为0时,最终的hashCode始终为0,正如我刚刚遇到的具有大量属性的对象所经历的那样,在以下代码中:

hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();

我建议:

hashCode = hashCode * _fieldPrimeNumber + fields[i].GetHashCode();

或类似于xor的类似this

hashCode = hashCode * _fieldPrimeNumber ^ fields[i].GetHashCode();