阅读 StackOverflow 上有关重写的所有问题和答案后 GetHashCode() 我编写了以下扩展方法,以便轻松方便地覆盖 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);
    }

您发现这段代码有什么问题吗?

有帮助吗?

解决方案

这看起来像一个固体的方式来做到这一点。

我唯一的建议是,如果你真的关心它的性能,您可能希望增加仿制药的几种常见的情况下(即大概1-4参数)。这样一来,对于那些对象(最有可能是小,关键式复合对象),你不会有建造阵列的开销传递到方法,循环,通用值的任何拳击等呼叫语法将是完全一样的,但你的这种情况下,运行稍微优化代码。当然,我会运行一些测试PERF在这个你决定是否值得维护的权衡了。

像这样:

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

其他提示

我写了一些东西,一会儿回来,你可能会解决你的问题......(而实际上,它也许可以改进,包括你有种子...)

总之,项目被称为精华( http://essence.codeplex.com/ ),和它使用的System.Linq.Expression库生成(基于属性)等于/ GetHashCode的/的CompareTo /的ToString,以及能够根据一个参数列表上创建的IEqualityComparer和IComparer接口的类的标准表示。 (我也有一些进一步的想法,但想继续太多之前还得到一些社区的反馈。)

(这意味着,它几乎一样快是手写 - 主之一,它不是的CompareTo();使所述Linq.Expressions不具有变量的概念在3.5版本 - 所以你必须给底层的对象上调用的CompareTo()两次,当你没有得到匹配。使用DLR扩展Linq.Expressions解决这个问题。我想我可以用信号发送的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;
        }
    ...
    }

反正项目,如果有人认为是值得的,需要打磨,但这些想法都没有......

我小艾对我好,我只有一个问题:这是你必须使用一个object[]中的值传递,因为这将框发送给函数的任何值类型一种耻辱。我不认为你有多少选择的余地,虽然,除非你去创造一些通用的重载像其他人所说的路线。

在你应该范围的unchecked狭义你可以合理的一般原则,但它没有多大意义在这里。除此之外,看起来很好。

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

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

更优化:

  1. 创建一个代码生成器,该生成器使用反射来查看业务对象字段,并创建一个覆盖 GetHashCode()(和 Equals())的新分部类。
  2. 当程序在调试模式下启动时运行代码生成器,如果代码已更改,则退出并向开发人员发送一条消息以重新编译。

这样做的优点是:

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

缺点:

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

我应该指出,在实施Gethashcode时,您几乎不应该进行分配(以下是 一些 有用 博客 关于它的帖子)。

那样的方式 params 有效(动态生成一个新数组)意味着这实际上不是一个好的通用解决方案。您最好对每个字段使用方法调用,并将哈希状态作为传递给它们的变量来维护(这使得使用更好的哈希函数和雪崩也变得容易)。

除了使用params object[] fields所产生的问题,我想不使用类型信息可能在某些情况下,性能问题了。假设两个班AB具有字段的相同类型和数量,实现相同的接口I。现在,如果你把AB对象与平等领域和不同类型的一个Dictionary<I, anything>对象将在同一个桶中结束。我可能会像插入一些hashCode ^= GetType().GetHashCode();声明

乔纳森·鲁普与参数数组,但接受的答案的交易不涉及价值类型的拳击。所以,如果性能是非常重要的我可能已经声明不反对GetHashCodeFromFieldsint参数,并发送未领域本身,而是字段的哈希码。即。

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

可能出现的一个问题是当乘法命中0,最终的hashCode始终为0,如我刚与很多的属性的对象经历,在下面的代码:

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

我建议:

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

或者与像此内容 XOR类似的东西:

hashCode = hashCode * _fieldPrimeNumber ^ fields[i].GetHashCode();
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top