Dav*_*rab 1371 c# overriding hashcode
鉴于以下课程
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
Run Code Online (Sandbox Code Playgroud)
我已经覆盖了该Equals
方法,因为它Foo
代表了Foo
s表的一行.哪个是覆盖的首选方法GetHashCode
?
覆盖为什么重要GetHashCode
?
Mar*_*ell 1258
是的,重要的是,您的项目将用作字典中的键,或者HashSet<T>
等等 - 因为这是用于(在没有自定义的情况下IEqualityComparer<T>
)将项目分组到存储桶中.如果两个项的哈希码不匹配,它们可能永远不会被认为是相等的(Equals
将永远不会被调用).
该GetHashCode()
方法应反映Equals
逻辑; 规则是:
Equals(...) == true
)那么它们必须返回相同的值GetHashCode()
GetHashCode()
是相等的,它是不必要对他们是相同的; 这是一次碰撞,Equals
将被调用以查看它是否是真正的平等.在这种情况下,看起来" return FooId;
"是一个合适的GetHashCode()
实现.如果您正在测试多个属性,通常使用下面的代码组合它们,以减少对角线冲突(即,new Foo(3,5)
具有不同的哈希码new Foo(5,3)
):
unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;
}
Run Code Online (Sandbox Code Playgroud)
哦 - 为了方便起见,你也可以考虑提供==
和!=
操作员覆盖Equals
和GetHashCode
.
当你弄错了会发生什么事的证明就在这里.
Alb*_*bic 131
实际上很难GetHashCode()
正确实现,因为除了Marc已经提到的规则之外,哈希代码在对象的生命周期内不应该改变.因此,用于计算哈希码的字段必须是不可变的.
当我使用NHibernate时,我终于找到了解决这个问题的方法.我的方法是从对象的ID计算哈希码.只能通过构造函数设置ID,因此如果要更改ID,这是非常不可能的,您必须创建一个具有新ID的新对象,因此需要新的哈希代码.这种方法最适用于GUID,因为您可以提供随机生成ID的无参数构造函数.
Tra*_*rap 54
通过重写Equals,您基本上声明自己是更了解如何比较给定类型的两个实例的人,因此您很可能是提供最佳哈希码的最佳候选者.
这是ReSharper如何为您编写GetHashCode()函数的示例:
public override int GetHashCode()
{
unchecked
{
var result = 0;
result = (result * 397) ^ m_someVar1;
result = (result * 397) ^ m_someVar2;
result = (result * 397) ^ m_someVar3;
result = (result * 397) ^ m_someVar4;
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,它只是尝试根据类中的所有字段猜测一个好的哈希代码,但由于您知道对象的域或值范围,您仍然可以提供更好的哈希代码.
huh*_*uha 40
请不要忘记null
在覆盖时检查obj参数Equals()
.并且还比较了类型.
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
Run Code Online (Sandbox Code Playgroud)
原因是:Equals
必须在比较时返回false null
.另请参见http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx
Lud*_*kov 32
怎么样:
public override int GetHashCode()
{
return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}
Run Code Online (Sandbox Code Playgroud)
假设性能不是问题:)
Ian*_*ose 13
我们有两个问题需要解决.
GetHashCode()
如果可以更改对象中的任何字段,则无法提供合理的信息.通常,对象永远不会用在依赖于的集合中 GetHashCode()
.因此,实施的成本GetHashCode()
通常是不值得的,或者是不可能的.
如果有人将您的对象放入一个调用的集合中
GetHashCode()
并且您已经覆盖Equals()
而没有
GetHashCode()
以正确的方式表现,那么该人可能需要花费数天来追踪问题.
所以默认我这样做.
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Some comment to explain if there is a real problem with providing GetHashCode()
// or if I just don't see a need for it for the given class
throw new Exception("Sorry I don't know what GetHashCode should do for this class");
}
}
Run Code Online (Sandbox Code Playgroud)
l33*_*33t 13
作为.NET 4.7
覆盖的首选方法GetHashCode()
如下所示。如果面向较旧的 .NET 版本,请包含System.ValueTuple nuget包。
// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();
Run Code Online (Sandbox Code Playgroud)
在性能方面,此方法将优于大多数复合哈希码实现。该ValueTuple是struct
这样不会有任何垃圾,以及底层的算法是一样快,因为它得到。
kem*_*002 11
这是因为框架要求两个相同的对象必须具有相同的哈希码.如果重写equals方法对两个对象进行特殊比较,并且方法认为两个对象相同,则两个对象的哈希码也必须相同.(字典和Hashtables依赖于这个原则).
Bor*_*ode 11
只是添加以上答案:
如果不重写等于,则默认行为是比较对象的引用.这同样适用于hashcode - 默认的implmentation通常基于引用的内存地址.因为你确实覆盖了Equals,所以它意味着正确的行为是比较你在Equals而不是引用上实现的任何东西,所以你应该对hashcode做同样的事情.
您的类的客户端将期望哈希码具有与equals方法类似的逻辑,例如使用IEqualityComparer的linq方法首先比较哈希码,并且只有当它们相等时它们才会比较可能更昂贵的Equals()方法要运行,如果我们没有实现hashcode,那么equ对象可能会有不同的hashcode(因为它们有不同的内存地址),并且会被错误地判断为不相等(Equals()甚至不会命中).
此外,除了您在字典中使用它时可能无法找到对象的问题(因为它是由一个哈希码插入的,当您查找它时,默认的哈希码可能会有所不同,而且Equals()甚至不会被调用,就像Marc Gravell在他的回答中解释的那样,你也引入了违反字典或hashset概念的行为,它不应该允许相同的键 - 你已经声明当你覆盖Equals时这些对象基本相同所以你不要不希望它们都是数据结构上的不同键,它们假设有一个唯一键.但是因为它们有不同的哈希码,所以"相同"键将作为不同的键插入.
散列代码用于基于散列的集合,如Dictionary,Hashtable,HashSet等.此代码的目的是通过将特定对象放入特定组(存储桶)来非常快速地对其进行预排序.当您需要从哈希集合中检索此对象时,这种预排序有助于找到此对象,因为代码必须仅在一个存储桶中而不是在其包含的所有对象中搜索您的对象.哈希码的更好分布(更好的唯一性)更快的检索.在每个对象具有唯一哈希码的理想情况下,找到它是O(1)操作.在大多数情况下,它接近O(1).
它不一定重要;它取决于集合的大小和性能要求,以及是否在可能不知道性能要求的库中使用您的类。我经常知道我的集合大小不是很大,并且我的时间比通过创建完美的哈希码获得几微秒的性能更有价值。因此(为了摆脱编译器的烦人警告),我只需使用:
public override int GetHashCode()
{
return base.GetHashCode();
}
Run Code Online (Sandbox Code Playgroud)
(当然,我也可以使用#pragma来关闭警告,但我更喜欢这种方式。)
当然,当您确实需要表现时,这里其他人提到的所有问题都适用。最重要的是 -否则从哈希集或字典中检索项目时,您会得到错误的结果:哈希码不得随对象的生存时间而变化(更准确地说,在需要哈希码的时间段内,例如字典中的键):例如,以下错误是由于Value是公共的,因此可以在实例的生存期内在类的外部进行更改,因此您不得将其用作哈希代码的基础:
class A
{
public int Value;
public override int GetHashCode()
{
return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
}
}
Run Code Online (Sandbox Code Playgroud)
另一方面,如果无法更改Value,则可以使用:
class A
{
public readonly int Value;
public override int GetHashCode()
{
return Value.GetHashCode(); //OK Value is read-only and can't be changed during the instance's life time
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
您应该始终保证,如果两个对象相等(如 Equals() 所定义的那样),它们应该返回相同的哈希码。正如其他一些评论所述,理论上,如果对象永远不会在基于哈希的容器(如 HashSet 或 Dictionary)中使用,则这不是强制性的。不过,我建议您始终遵守这条规则。原因很简单,因为对于某些人来说,出于实际提高性能或只是以更好的方式传达代码语义的良好意图,将集合从一种类型更改为另一种类型太容易了。
例如,假设我们在列表中保存一些对象。一段时间后,有人实际上意识到 HashSet 是一个更好的选择,因为它具有更好的搜索特性。这就是我们可能遇到麻烦的时候。List 将在内部使用该类型的默认相等比较器,这意味着在您的情况下等于,而 HashSet 使用 GetHashCode()。如果两者的行为不同,你的程序也会不同。请记住,此类问题并不是最容易解决的。
我在一篇博客文章中总结了这种行为以及其他一些 GetHashCode() 陷阱,您可以在其中找到更多示例和解释。
归档时间: |
|
查看次数: |
352861 次 |
最近记录: |