可以调用GetHashCode作为从Equals覆盖内部测试相等性的方法吗?
例如,此代码是否可以接受?
public class Class1
{
public string A
{
get;
set;
}
public string B
{
get;
set;
}
public override bool Equals(object obj)
{
Class1 other = obj as Class1;
return other != null && other.GetHashCode() == this.GetHashCode();
}
public override int GetHashCode()
{
int result = 0;
result = (result ^ 397) ^ (A == null ? 0 : A.GetHashCode());
result = (result ^ 397) ^ (B == null ? 0 : B.GetHashCode());
return result;
}
}
答案 0 :(得分:14)
其他人是对的;你的平等操作被打破了。举例说明:
public static void Main()
{
var c1 = new Class1() { A = "apahaa", B = null };
var c2 = new Class1() { A = "abacaz", B = null };
Console.WriteLine(c1.Equals(c2));
}
我想你想要那个程序的输出是“假的”,但是你对CLR的某些实现的相等定义是“真的”。
请记住,只有大约40亿个可能的哈希码。有超过40亿个可能的六个字母字符串,而因此至少有两个字母字符串具有相同的哈希码。我给你看了两个;还有更多的东西。
一般情况下,如果有n个可能的哈希码,那么一旦你了解了n个元素的平方根,那么获得冲突的几率会急剧上升。这就是所谓的“生日悖论”。关于为什么不应该依赖哈希码来实现相等性的文章,请参阅:
http://blogs.msdn.com/b/ericlippert/archive/2010/03/22/socks-birthdays-and-hash-collisions.aspx
答案 1 :(得分:7)
不,不行,因为不是
equality <=> hashcode equality
。
只是
equality => hashcode equality
。
或在另一个方向:
hashcode inequality => inequality
。
引用http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx:
如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象的比较不相等,则两个对象的GetHashCode方法不必返回不同的值。
答案 2 :(得分:2)
我会说,除非您希望Equals
基本上表示“与您的类型具有相同的哈希码”,然后否,因为两个字符串可能不同但是共享相同的哈希码。概率可能很小,但不是零。
答案 3 :(得分:1)
这不是测试平等的可接受方式。 2个不相等的值很可能具有相同的哈希码。这会导致您Equals
的实施在返回true
时返回false
答案 4 :(得分:1)
您可以调用GetHashCode
来确定项目是否不相等,但如果两个对象返回相同的哈希码,则并不意味着它们 >平等。两个项目可以具有相同的哈希码但不相等。
如果比较两个项目的成本很高,那么您可以比较哈希码。如果他们不平等,那么你可以保释。否则(哈希码相等),你必须进行全面比较。
例如:
public override bool Equals(object obj)
{
Class1 other = obj as Class1;
if (other == null || other.GetHashCode() != this.GetHashCode())
return false;
// the hash codes are the same so you have to do a full object compare.
}
答案 5 :(得分:1)
你不能说只是因为哈希码是相等的,那么对象必须是相等的。
在GetHashCode
内调用Equals
的唯一一次是,如果为对象计算哈希值(比如因为缓存它)要比检查相等性要便宜得多。在这种情况下,您可以说if (this.GetHashCode() != other.GetHashCode()) return false;
,以便您可以快速验证对象不相等。
所以你什么时候做到这一点?我写了一些代码,它定期截取屏幕截图,并试图找出屏幕更改后的时间。由于我的屏幕截图是8MB并且在屏幕截图间隔内相对较少的像素发生变化,因此搜索它们的列表以查找哪些相同是相当昂贵的。哈希值很小,每个屏幕截图只需要计算一次,这样就很容易消除已知的不相等的哈希值。实际上,在我的应用程序中,我认为具有相同的哈希值足够接近等于我甚至懒得实现Equals
重载,导致C#编译器警告我我正在超载{{1}没有重载GetHashCode
。
答案 6 :(得分:0)
有一种情况是使用哈希码作为相等比较的捷径是有道理的。
考虑您正在构建哈希表或哈希集的情况。实际上,我们只考虑一下hashsets(哈希表通过保存一个值来扩展它,但这不相关)。
可以采用各种不同的方法,但是在所有这些方法中,您可以放置少量的散列值,我们采用开放或封闭的方式(这只是为了好玩,有些人使用与其他人相反的行话);如果我们在同一个插槽上碰撞两个不同的对象,我们可以将它们存储在同一个插槽中(但是有一个链接列表或实际存储对象的位置)或者通过重新探测来选择不同的插槽(有各种各样的插槽)对此的策略。
现在,无论使用哪种方法,我们都会使用散列表从O(1)复杂度转向O(n)复杂度。这种风险与可用的插槽数量成反比,因此在一定大小之后我们调整哈希表的大小(即使一切都很理想,如果存储的项目数大于数量,我们最终还是必须这样做时隙)。
重新插入调整大小的项目显然取决于哈希码。因此,虽然在一个对象中记忆GetHashCode()
很少有意义(它在大多数对象上都没有经常被调用),但在哈希表本身(也许,或许,它)中记忆它确实是有意义的。记住生成的结果,例如,如果你使用Wang / Jenkins哈希重新散列,以减少由不良GetHashCode()
实现造成的损害。
现在,当我们来插入时,我们的逻辑就像是:
因此,在这种情况下,我们必须在比较相等性之前获取哈希码。我们还有已经预先计算的现有对象的哈希码,以允许调整大小。这两个事实的结合意味着将第4项的比较实施为:
private bool IsMatch(KeyType newItem, KeyType storedItem, int newHash, int oldHash)
{
return ReferenceEquals(newItem, storedItem) // fast, false negatives, no false positives (only applicable to reference types)
||
(
newHash == oldHash // fast, false positives, no fast negatives
&&
_cmp.Equals(newItem, storedItem) // slow for some types, but always correct result.
);
}
显然,这样做的好处取决于_cmp.Equals
的复杂性。如果我们的密钥类型是int
,那么这将是完全浪费。如果我们的键类型中的字符串和我们使用不区分大小写的Unicode规范化的相等比较(因此它甚至不能在长度上快捷方式)那么保存可能是值得的。
通常,记忆哈希码没有意义,因为它们的使用频率不足以获得性能,但将它们存储在哈希集或哈希表本身中是有意义的。
答案 7 :(得分:0)
这是错误的实施,正如其他人所说的那样。
您应该使用GetHashCode
短路检查相等性,如:
if (other.GetHashCode() != this.GetHashCode()
return false;
在Equals
方法中,只有当您确定随后的Equals实施费用比GetHashCode
贵得多时,这绝不是绝大多数情况。
在你展示的这一个实现中(99%的情况)它不仅被破坏,而且还慢得多。原因呢? 计算你的属性的哈希几乎肯定比比较它们慢,所以你甚至没有获得性能方面的优势。实现正确GetHashCode
的优点是,当您的类可以是哈希表的关键类型时,哈希只计算一次(并且该值用于比较)。在您的情况下,GetHashCode
将被多次调用,如果它在一个集合中。尽管GetHashCode
本身应该很快,但它并不比等效 Equals
快。
要进行基准测试,请运行Equals
(正确的实现,取出当前基于哈希的实现)和GetHashCode
此处
var watch = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
action(); //Equals and GetHashCode called here to test for performance.
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);