C# 当重写Equals方法时,为什么重写GetHashCode很重要?
给定以下类C# 当重写Equals方法时,为什么重写GetHashCode很重要?,c#,overriding,hashcode,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;
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();
}
}
我已经重写了Equals
方法,因为Foo
表示Foo
s表的一行。覆盖GetHashCode
的首选方法是什么
为什么覆盖
GetHashCode
很重要?是的,如果您的项将用作字典中的键,或者HashSet
等,这一点很重要,因为这是用来(在没有自定义IEqualityComparer
的情况下)将项分组到存储桶中的。如果两个项目的哈希代码不匹配,则它们可能永远不会被视为相等(根本不会被调用)
方法应反映等于逻辑;这些规则是:
- 如果两个值相等(
Equals(…)==true
),则它们必须为GetHashCode()
- 如果
GetHashCode()
相等,则它们不必相同;这是一个冲突,将调用Equals
,查看它是否为真正的相等
在本例中,看起来“returnfooid;
”是一个合适的GetHashCode()
实现。如果您正在测试多个属性,通常会使用如下代码将它们组合在一起,以减少对角冲突(即,new Foo(3,5)
与new Foo(5,3)
具有不同的哈希代码):
OH-为了方便起见,您也可以考虑提供<代码>=< /代码>和<代码>!重写
等于
和GetHashCode
时的code>运算符
当您出错时会发生什么的一个示例是。这是因为框架要求相同的两个对象必须具有相同的哈希代码。如果重写equals方法对两个对象进行特殊比较,并且该方法认为这两个对象相同,那么这两个对象的哈希代码也必须相同。(字典和哈希表依赖于这一原理)。通过重写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;
}
}
正如您所看到的,它只是试图根据类中的所有字段猜测一个好的哈希代码,但由于您知道对象的域或值范围,因此仍然可以提供一个更好的。实际上很难正确实现GetHashCode()
,因为除了前面提到的规则之外,哈希代码在对象的生存期内不应更改。因此,用于计算哈希代码的字段必须是不可变的
当我与NHibernate合作时,我终于找到了这个问题的解决方案。
我的方法是根据对象的ID计算哈希代码。ID只能通过构造函数进行设置,因此如果要更改ID(这是不太可能的),则必须创建一个新对象,该对象具有新ID,因此具有新的哈希代码。这种方法最适用于GUID,因为您可以提供一个无参数构造函数,随机生成ID。如何:
public override int GetHashCode()
{
return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}
假设性能不是问题:)
这不一定重要;这取决于集合的大小和性能要求,以及您的类是否将在您可能不知道性能要求的库中使用。我经常知道我的集合大小不是很大,我的时间比创建一个完美的哈希代码所获得的几微秒的性能更有价值;因此(为了消除编译器发出的恼人警告),我只需使用:
public override int GetHashCode()
{
return base.GetHashCode();
}
(当然,我也可以使用#pragma来关闭警告,但我更喜欢这种方式。)
当然,当你处于需要绩效的位置时,这里其他人提到的所有问题都适用最重要的-否则从哈希集或字典中检索项目时会得到错误的结果:哈希代码不得随对象的生命周期而变化(更准确地说,在需要哈希代码时,例如当哈希代码是字典中的键时):例如,以下是错误的,因为值是公共的,因此可以在实例的生命周期内从外部更改为类,因此不能将其用作哈希代码的基础:
class A
{
public int Value;
public override int GetHashCode()
{
return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
}
}
另一方面,如果值无法更改,则可以使用:
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
}
}
重写Equals()
时,请不要忘记对照null
检查obj参数。
并对其类型进行了比较
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
原因是:Equals
在与null
比较时必须返回false。另请参见哈希代码用于基于哈希的集合,如字典、哈希表、哈希集等。此代码的目的是通过将特定对象放入特定组(bucket)来非常快速地对其进行预排序。当您需要从散列集合中检索该对象时,这种预排序非常有助于查找该对象,因为代码必须在一个bucket中而不是在它包含的所有对象中搜索对象。散列码的分布越好(唯一性越好),检索速度越快。在理想情况下,每个对象都有一个唯一的哈希代码,查找它是一个O(1)操作。在大多数情况下,它接近O(1)。据我所知,原始GetHashCode()返回对象的内存地址,因此,如果您希望比较两个不同的对象,则必须重写它
编辑:
这是不正确的,原始的GetHashCode()方法不能保证2个值相等。虽然相等的对象返回相同的哈希代码 我们有两个问题要处理
如果有字段,则不能提供合理的GetHashCode()
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
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");
}
}
public int getHashCode()
{
PropertyInfo[] theProperties = this.GetType().GetProperties();
int hash = 31;
foreach (PropertyInfo info in theProperties)
{
if (info != null)
{
var value = info.GetValue(this,null);
if(value != null)
unchecked
{
hash = 29 * hash ^ value.GetHashCode();
}
}
}
return hash;
}