GetHashCode
方法 在许多.NET基类库中被广泛使用。正确实现它对于快速查找集合中的项或确定相等性尤为重要。
是否有标准算法或最佳实践来为我的自定义类实现GetHashCode
,以免降低性能?GetHashCode
方法 在许多.NET基类库中被广泛使用。正确实现它对于快速查找集合中的项或确定相等性尤为重要。
是否有标准算法或最佳实践来为我的自定义类实现GetHashCode
,以免降低性能?通常我会使用Josh Bloch在他精彩的Effective Java书中提供的实现。它快速并且创建了一个相当不容易造成碰撞的哈希值。选择两个不同的质数,例如17和23,然后进行以下操作:
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + field1.GetHashCode();
hash = hash * 23 + field2.GetHashCode();
hash = hash * 23 + field3.GetHashCode();
return hash;
}
}
正如评论中所指出的那样,您可能会发现最好选择一个大的素数来进行乘法运算。显然,486187739是不错的选择...虽然我看到的大多数使用小数的示例通常使用质数,但至少有一些类似的算法经常使用非质数。例如,在稍微改变的FNV示例中,我使用了一些可以很好地工作的数字,但初始值不是质数。(不过,乘法常数是质数。我不知道这有多重要。)
这比常见做法——对哈希码进行XOR
——要好,主要有两个原因。假设我们有一个带有两个int
字段的类型:
XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y
顺便提一下,早期的算法是C#编译器目前用于匿名类型的算法。
这个页面提供了很多选择。我认为对于大多数情况来说,以上方法已经足够好,并且非常容易记住和实现。 FNV 替代方案同样简单,但使用不同的常量,使用 XOR 而不是 ADD 作为组合操作。它看起来像下面的代码,但正常的 FNV 算法是针对每个字节进行操作的,因此需要修改以执行每个字节而不是每个32位哈希值的迭代。FNV 还设计用于可变长度的数据,而我们在这里使用它的方式总是相同数量的字段值。对这个答案的评论表明,这里的代码在(测试的)示例情况下实际上并不像上面的加法方法那样有效。
// Note: Not quite FNV!
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = (int) 2166136261;
// Suitable nullity checks etc, of course :)
hash = (hash * 16777619) ^ field1.GetHashCode();
hash = (hash * 16777619) ^ field2.GetHashCode();
hash = (hash * 16777619) ^ field3.GetHashCode();
return hash;
}
}
需要注意的一件事是,理想情况下,您应该在将敏感于相等性(因此敏感于哈希码)的状态添加到依赖于哈希码的集合之后,防止其更改。
根据文档:
对于不可变的引用类型,可以重写 GetHashCode 。通常,对于可变的引用类型,只有在以下情况下才应覆盖 GetHashCode :
- 可以从不可变字段计算哈希码;或者
- 可以确保可变对象的哈希码在包含它的依赖于其哈希码的集合中不会更改。
FNV 文章的链接已失效,但是在 Internet Archive 中有备份: Eternally Confuzzled - The Art of Hashing
Dictionary<TKey,TValue>
假定在某些质数下具有良好的分布。而23就是其中之一。因此,如果您有一个容量为23的字典,那么只有最后一个对GetHashCode
方法的贡献会影响复合哈希码。所以我更愿意使用29而不是23。 - CodesInChaos正如@cactuaroid在评论中提到的,可以使用值元组(ValueTuple)。这样可以节省一些击键,并且更重要的是纯粹在堆栈上执行(没有垃圾):
(PropA, PropB, PropC, PropD).GetHashCode();
(注:使用匿名类型的原始技术似乎会在堆上创建一个对象,即垃圾,因为匿名类型实现为类,尽管编译器可能会优化掉这个问题。对这些选项进行基准测试将是有趣的,但元组选项应该更优。)
微软已经提供了一个很好的通用HashCode生成器:只需将您的属性/字段值复制到匿名类型中并进行哈希处理:
new { PropA, PropB, PropC, PropD }.GetHashCode();
这将适用于任何数量的属性。它不使用装箱,只是使用框架中已实现的匿名类型算法。
GetHashCode
实现非常有效(顺便说一句,这与 Jon Skeet 的答案中的实现相同),但是这种解决方案的唯一问题在于每次调用 GetHashCode
都会生成一个新的实例。在访问大型散列集合时可能会有点开销。 - digEmAllNew With {Key PropA}.GetHashCode()
,否则对于具有相同“标识”属性的不同对象,GetHashCode将不会返回相同的哈希码。 - David Osborne(PropA, PropB, PropC, PropD).GetHashCode()
现在可以在 C#7 上使用,而无需担心 GC 压力,以回应 @digEmAll 的关注。快速简单的哈希码组合 - cactuaroidSystem.HashCode
如果你使用的是 .NET Standard 2.1 或以上版本,可以使用 System.HashCode 结构体。在早期的框架中,可以通过 Microsoft.Bcl.HashCode
包获得。有两种使用方法:
Combine
方法可用于创建哈希码,最多可给出八个对象。
public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);
Add
方法可以帮助你处理集合:
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(this.object1);
foreach (var item in this.collection)
{
hashCode.Add(item);
}
return hashCode.ToHashCode();
}
这是一个替代System.HashCode
的方法,使用起来非常简单,同时速度也很快。您可以阅读完整的博客文章'HashCode简化'以获取更多细节和评论。
public class SuperHero
{
public int Age { get; set; }
public string Name { get; set; }
public List<string> Powers { get; set; }
public override int GetHashCode() =>
HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}
public struct HashCode : IEquatable<HashCode>
{
private const int EmptyCollectionPrimeNumber = 19;
private readonly int value;
private HashCode(int value) => this.value = value;
public static implicit operator int(HashCode hashCode) => hashCode.value;
public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);
public static bool operator !=(HashCode left, HashCode right) => !(left == right);
public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));
public static HashCode OfEach<T>(IEnumerable<T> items) =>
items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));
public HashCode And<T>(T item) =>
new HashCode(CombineHashCodes(this.value, GetHashCode(item)));
public HashCode AndEach<T>(IEnumerable<T> items)
{
if (items == null)
{
return new HashCode(this.value);
}
return new HashCode(GetHashCode(items, this.value));
}
public bool Equals(HashCode other) => this.value.Equals(other.value);
public override bool Equals(object obj)
{
if (obj is HashCode)
{
return this.Equals((HashCode)obj);
}
return false;
}
public override int GetHashCode() => this.value.GetHashCode();
private static int CombineHashCodes(int h1, int h2)
{
unchecked
{
// Code copied from System.Tuple a good way to combine hashes.
return ((h1 << 5) + h1) ^ h2;
}
}
private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;
private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
{
var temp = startHashCode;
var enumerator = items.GetEnumerator();
if (enumerator.MoveNext())
{
temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
while (enumerator.MoveNext())
{
temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
}
}
else
{
temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
}
return temp;
}
}
计算哈希码的算法需要快速。简单的算法通常会更快。不分配额外的内存也将减少垃圾回收的需求,从而提高性能。
在 C# 哈希函数中,通常使用 unchecked
关键字来停止溢出检查以提高性能。
哈希算法需要是确定性的,即给定相同的输入,它必须始终产生相同的输出。
计算哈希码的算法需要将哈希冲突保持到最小。哈希冲突是指当两个不同对象上的两个调用 GetHashCode
产生相同的哈希码时发生的情况。请注意,允许发生冲突(有些人误解为不允许),但应该尽量减少。
许多哈希函数包含像 17
或 23
这样的特殊质数,由于它们的数学特性,可以帮助减少哈希冲突,而不是使用非质数。
<?xml version ="1.0"?>
<configuration>
<runtime>
<UseRandomizedStringHashAlgorithm enabled="1" />
</runtime>
</configuration>
由于这个特性,哈希码不应在创建它们的应用程序域之外使用,不应作为集合中的关键字段使用,也不应持久化存储。
在此处了解更多信息。
加密安全吗?
该算法不必是加密哈希函数。意味着它不必满足以下条件:
- 生成一个消息以产生给定哈希值是不可行的。
- 找到两个具有相同哈希值的不同消息是不可行的。
- 对消息进行微小更改应更改哈希值,使得新哈希值与旧哈希值不相关(雪崩效应)。
HashCode
类型也满足此要求。 - TimoValueTuple.GetHashCode()
答案相比如何? - Thiago SilvaHashCode.Combine
是一个静态方法,不会分配任何内存,而ValueTuple
会从堆栈开始分配内存。 - Muhammad Rehan SaeedHashCode.Of(this.Name).And(this.Age).AndEach(this.Powers)
- 这是很好的语法 :) - Amos Egel这是我的哈希码助手。
它的优点在于使用了通用类型参数,因此不会导致装箱:
public static class HashHelper
{
public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
{
unchecked
{
return 31 * arg1.GetHashCode() + arg2.GetHashCode();
}
}
public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
{
unchecked
{
int hash = arg1.GetHashCode();
hash = 31 * hash + arg2.GetHashCode();
return 31 * hash + arg3.GetHashCode();
}
}
public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3,
T4 arg4)
{
unchecked
{
int hash = arg1.GetHashCode();
hash = 31 * hash + arg2.GetHashCode();
hash = 31 * hash + arg3.GetHashCode();
return 31 * hash + arg4.GetHashCode();
}
}
public static int GetHashCode<T>(T[] list)
{
unchecked
{
int hash = 0;
foreach (var item in list)
{
hash = 31 * hash + item.GetHashCode();
}
return hash;
}
}
public static int GetHashCode<T>(IEnumerable<T> list)
{
unchecked
{
int hash = 0;
foreach (var item in list)
{
hash = 31 * hash + item.GetHashCode();
}
return hash;
}
}
/// <summary>
/// Gets a hashcode for a collection for that the order of items
/// does not matter.
/// So {1, 2, 3} and {3, 2, 1} will get same hash code.
/// </summary>
public static int GetHashCodeForOrderNoMatterCollection<T>(
IEnumerable<T> list)
{
unchecked
{
int hash = 0;
int count = 0;
foreach (var item in list)
{
hash += item.GetHashCode();
count++;
}
return 31 * hash + count.GetHashCode();
}
}
/// <summary>
/// Alternative way to get a hashcode is to use a fluent
/// interface like this:<br />
/// return 0.CombineHashCode(field1).CombineHashCode(field2).
/// CombineHashCode(field3);
/// </summary>
public static int CombineHashCode<T>(this int hashCode, T arg)
{
unchecked
{
return 31 * hashCode + arg.GetHashCode();
}
}
也有扩展方法提供流畅的接口,所以您可以像这样使用它:
public override int GetHashCode()
{
return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}
或者像这样:
public override int GetHashCode()
{
return 0.CombineHashCode(Manufacturer)
.CombineHashCode(PartN)
.CombineHashCode(Quantity);
}
我有一个帮助库中的哈希类,我用它来实现这个目的。
/// <summary>
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
const int b = 378551;
int a = 63689;
int hash = 0;
// If it overflows then just wrap around
unchecked
{
for (int i = 0; i < input.Length; i++)
{
if (input[i] != null)
{
hash = hash * a + input[i].GetHashCode();
a = a * b;
}
}
}
return hash;
}
然后,你可以简单地将它用作:public override int GetHashCode()
{
return Hashing.RSHash(_field1, _field2, _field3);
}
我没有评估它的表现,所以欢迎任何反馈。
unchecked
的整个意义在于避免在GetHashCode
上溢出时发生异常。因此,如果值超过了int
,也不会有任何影响,它并不是错误的。 - Tim Schmelternull
可能会给您带来意想不到的结果。您应该在input[i]
为null时使用一些常量值,而不是跳过它们,而不是使用input[i].GetHashCode()
。 - David Schwartzpublic static class HashCode
{
public const int Start = 17;
public static int Hash<T>(this int hash, T obj)
{
var h = EqualityComparer<T>.Default.GetHashCode(obj);
return unchecked((hash * 31) + h);
}
}
使用方法:
public override int GetHashCode()
{
return HashCode.Start
.Hash(_field1)
.Hash(_field2)
.Hash(_field3);
}
如果您想避免编写 System.Int32 的扩展方法:
public readonly struct HashCode
{
private readonly int _value;
public HashCode(int value) => _value = value;
public static HashCode Start { get; } = new HashCode(17);
public static implicit operator int(HashCode hash) => hash._value;
public HashCode Hash<T>(T obj)
{
var h = EqualityComparer<T>.Default.GetHashCode(obj);
return unchecked(new HashCode((_value * 31) + h));
}
public override int GetHashCode() => _value;
}
它仍然避免了任何堆分配,并且使用方式完全相同:
public override int GetHashCode()
{
// This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
// And the result is implicitly converted to `Int32`.
return HashCode.Start
.Hash(_field1)
.Hash(_field2)
.Hash(_field3);
}
编辑(2018年5月):EqualityComparer<T>.Default
属性现在是JIT内置的。这个拉取请求被Stephen Toub提到,在这篇博客文章中提到了它。
var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
- Bill Barryobj != null
的三元运算符将编译为一个 box
指令,如果 T
是值类型,则会分配内存。相反,您可以使用 obj.Equals(null)
,它将编译为 Equals
方法的虚拟调用。 - Martin Liversagethis.hashCode != h
,所以它不会返回相同的值。 - Şafak GürGetHashCode
执行内存分配方面没有问题,只要它仅在第一次使用时执行(随后的调用只返回缓存结果)。重要的是不应该费尽心思避免冲突,而是应该避免“系统性”冲突。如果一个类型有两个int
字段oldX
和newX
,它们经常相差一,那么oldX^newX
的哈希值将会给这样的记录分配90%的哈希值1、2、4或8。使用oldX+newX
[未检查的算术]可能会生成更多的冲突... - supercatpublic static int ReHash(int source)
{
unchecked
{
ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
ulong d = 0xE2ADBEEFDEADBEEF ^ c;
ulong a = d += c = c << 15 | c >> -15;
ulong b = a += d = d << 52 | d >> -52;
c ^= b += a = a << 26 | a >> -26;
d ^= c += b = b << 51 | b >> -51;
a ^= d += c = c << 28 | c >> -28;
b ^= a += d = d << 9 | d >> -9;
c ^= b += a = a << 47 | a >> -47;
d ^= c += b << 54 | b >> -54;
a ^= d += c << 32 | c >> 32;
a += d << 25 | d >> -25;
return (int)(a >> 1);
}
}
然后我的二次幂哈希表不再糟糕了。但这让我感到困扰,因为上述方法本不应奏效。更确切地说,除非原始的 GetHashCode() 以特定的方式较差,否则它不应奏效。
重新混合哈希码无法改善很好的哈希码,因为唯一可能的影响是引入更多冲突。
重新混合哈希码无法改善可怕的哈希码,因为唯一可能的影响是将值为53的大量冲突更改为183487291的大量冲突。
重新混合哈希码只能改善避免绝对冲突的哈希码,但在实际使用哈希表时却很差。虽然使用二次幂表格的简单取模使这更加明显,但在使用更常见的质数表格时也会产生负面影响,只是不太明显(重新哈希的额外工作会抵消收益,但仍然会有收益)。
编辑:我还使用了开放地址,这也增加了对冲突的敏感性,可能比二次幂更甚。
好的,关于编程方面的内容,我来为您翻译一下。在.NET(或者可以在这里了解)中,string.GetHashCode()
实现的质量有待提高,通过改进可以减少冲突,测试运行速度快了20-30倍,这让人感到不安,更令人不安的是我的哈希代码也可以得到大幅改善。
过去我编写的所有GetHashCode()实现方法,包括我在本站回答问题时使用的方法,都比我想象中要差得多。虽然很多时候这已经足够应对大部分情况,但我想要更好的方法。
所以我把那个项目放在一边(它只是一个个人项目),开始寻找如何快速在.NET中生成一个好的、分布均匀的哈希代码。
最终我选择将SpookyHash移植到.NET。事实上,上面的代码是使用SpookyHash从32位输入产生32位输出的快速路径版本。
现在,SpookyHash并不是一个容易记忆的好代码。我手动内联了很多内容以提高速度*,所以我对它进行了移植,使它变得更加不容易理解。但这就是代码重用的作用。 然后我把那个项目放到一边,因为原始项目产生了一个问题:如何生成更好的哈希码,而那个项目则产生了一个问题:如何生成更好的.NET memcpy。 然后我回来了,并产生了很多重载,以便轻松地将几乎所有本机类型(除了decimal
†)输入哈希码。
它很快,其中Bob Jenkins应该得到大部分的功劳,因为他的原始代码比我移植的代码更快,特别是在64位机器上,该算法经过了优化‡。
完整的代码可以在https://bitbucket.org/JonHanna/spookilysharp/src中查看,但请考虑上面的代码是其简化版本。
然而,既然它已经被写出来了,那么人们可以更容易地利用它:
public override int GetHashCode()
{
var hash = new SpookyHash();
hash.Update(field1);
hash.Update(field2);
hash.Update(field3);
return hash.Final().GetHashCode();
}
它还可以接受种子值,因此如果您需要处理不受信任的输入并希望防止哈希DoS攻击,可以基于运行时间等设置种子,并使攻击者无法预测结果。
private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
//produce different hashes ever time this application is restarted
//but remain consistent in each run, so attackers have a harder time
//DoSing the hash tables.
var hash = new SpookyHash(hashSeed0, hashSeed1);
hash.Update(field1);
hash.Update(field2);
hash.Update(field3);
return hash.Final().GetHashCode();
}
*在这里的一个大惊喜是,手动内联旋转方法并返回(x << n) | (x >> -n)
可以改善性能。我本以为即时编译器会自动内联它,但分析结果表明不是这样。
†从.NET的角度来看,decimal
并不是本地的,尽管在C#中是。它的问题在于其自己的GetHashCode()
将精度视为重要因素,而其自己的Equals()
则不是。这两种方式都是有效的选择,但不能混合使用。在实现自己的版本时,您需要选择其中一种,但我无法知道您想要哪个。
‡作为比较。如果用于字符串,64位的SpookyHash比32位的string.GetHashCode()
要快得多,而32位的string.GetHashCode()
略快于64位的string.GetHashCode()
,后者又比32位的SpookyHash快得多,尽管仍然足够快以成为合理的选择。
long
类型的中间结果,然后将最终结果压缩为int
类型。这样做是否明智?我的担忧是,如果使用例如hash=(hash*31)+nextField,那么匹配值对只会影响哈希的上27位。让计算扩展到long
类型并进行包装可以最小化这种危险。 - supercatReHash
有点过度了。我猜它能够正常工作,但它可能比加密哈希还要慢,而加密哈希已经被证明可以完美地工作。Java也使用大小为2的幂的表格,曾经有一个相当复杂的重新哈希过程。自从引入了树节点来处理冲突后,这个过程已经变得简化了。 - maaartinus截至https://github.com/dotnet/coreclr/pull/14863,现在有一种全新的生成哈希码的方法,非常简单!只需编写:
public override int GetHashCode()
=> HashCode.Combine(field1, field2, field3);
这将生成一个优质的哈希码,而无需您担心实现细节。
HashCode
的更改在你的评论几个小时前已经合并到了 corefx 中 :) 这种类型预计将在 .NET Core 2.1 中发布。 - James Ko这是一个好的例子:
/// <summary>
/// Helper class for generating hash codes suitable
/// for use in hashing algorithms and data structures like a hash table.
/// </summary>
public static class HashCodeHelper
{
private static int GetHashCodeInternal(int key1, int key2)
{
unchecked
{
var num = 0x7e53a269;
num = (-1521134295 * num) + key1;
num += (num << 10);
num ^= (num >> 6);
num = ((-1521134295 * num) + key2);
num += (num << 10);
num ^= (num >> 6);
return num;
}
}
/// <summary>
/// Returns a hash code for the specified objects
/// </summary>
/// <param name="arr">An array of objects used for generating the
/// hash code.</param>
/// <returns>
/// A hash code, suitable for use in hashing algorithms and data
/// structures like a hash table.
/// </returns>
public static int GetHashCode(params object[] arr)
{
int hash = 0;
foreach (var item in arr)
hash = GetHashCodeInternal(hash, item.GetHashCode());
return hash;
}
/// <summary>
/// Returns a hash code for the specified objects
/// </summary>
/// <param name="obj1">The first object.</param>
/// <param name="obj2">The second object.</param>
/// <param name="obj3">The third object.</param>
/// <param name="obj4">The fourth object.</param>
/// <returns>
/// A hash code, suitable for use in hashing algorithms and
/// data structures like a hash table.
/// </returns>
public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
T4 obj4)
{
return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
}
/// <summary>
/// Returns a hash code for the specified objects
/// </summary>
/// <param name="obj1">The first object.</param>
/// <param name="obj2">The second object.</param>
/// <param name="obj3">The third object.</param>
/// <returns>
/// A hash code, suitable for use in hashing algorithms and data
/// structures like a hash table.
/// </returns>
public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
{
return GetHashCode(obj1, GetHashCode(obj2, obj3));
}
/// <summary>
/// Returns a hash code for the specified objects
/// </summary>
/// <param name="obj1">The first object.</param>
/// <param name="obj2">The second object.</param>
/// <returns>
/// A hash code, suitable for use in hashing algorithms and data
/// structures like a hash table.
/// </returns>
public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
{
return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
}
}
以下是如何使用它的方法:
private struct Key
{
private Type _type;
private string _field;
public Type Type { get { return _type; } }
public string Field { get { return _field; } }
public Key(Type type, string field)
{
_type = type;
_field = field;
}
public override int GetHashCode()
{
return HashCodeHelper.GetHashCode(_field, _type);
}
public override bool Equals(object obj)
{
if (!(obj is Key))
return false;
var tf = (Key)obj;
return tf._field.Equals(_field) && tf._type.Equals(_type);
}
}
h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);
)存在代码异味:它们不依赖于任何输入,看起来非常冗余。 - sehe
GetHashCode
的重写。希望对他人有所帮助。由Eric Lippert编写的GetHashCode指南和规则 - reneEquals()
的情况下,仍然使用了GetHashCode()
函数。这就是我之前所说的意思。将GetHashCode()
嵌套在Equals()
中经常被用作快捷方式来确定不相等性,因为如果两个对象具有不同的哈希码,则它们必须是不相等的对象,而其余的相等性检查就不必执行了。 - bitbonkGetHashCode()
和Equals()
两者都需要查看两个对象的所有字段(如果哈希码相等或未检查,则Equals()
需要执行此操作)。因此,在Equals()
内部调用GetHashCode()
通常是多余的,并且可能会降低性能。Equals()
也可以进行短路运算,使其更快 - 但在某些情况下,哈希码可能已被缓存,从而使GetHashCode()
检查更快,因此值得使用。请参见此问题以了解更多信息。 - NotEnoughData