GetHashCode扩展方法
-
11-09-2019 - |
题
阅读 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);
}
(是的,我很迂腐,但是这是我看到的唯一问题)
更优化:
- 创建一个代码生成器,该生成器使用反射来查看业务对象字段,并创建一个覆盖 GetHashCode()(和 Equals())的新分部类。
- 当程序在调试模式下启动时运行代码生成器,如果代码已更改,则退出并向开发人员发送一条消息以重新编译。
这样做的优点是:
- 使用反射,您可以知道哪些字段是值类型,从而知道它们是否需要空检查。
- 没有开销 - 没有额外的函数调用,没有列表构造等。如果您要进行大量字典查找,这一点很重要。
- 长实现(在具有大量字段的类中)隐藏在部分类中,远离重要的业务代码。
缺点:
- 如果您不进行大量字典查找/调用 GetHashCode(),那就太过分了。
除了使用params object[] fields
所产生的问题,我想不使用类型信息可能在某些情况下,性能问题了。假设两个班A
,B
具有字段的相同类型和数量,实现相同的接口I
。现在,如果你把A
和B
对象与平等领域和不同类型的一个Dictionary<I, anything>
对象将在同一个桶中结束。我可能会像插入一些hashCode ^= GetType().GetHashCode();
声明
乔纳森·鲁普与参数数组,但接受的答案的交易不涉及价值类型的拳击。所以,如果性能是非常重要的我可能已经声明不反对GetHashCodeFromFields
但int
参数,并发送未领域本身,而是字段的哈希码。即。
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();