重写Equals方法时为何要重写GetHashCode方法

重写Equals方法时为何要重写GetHashCode方法

技术背景

在.NET 中,当我们重写 Equals 方法时,通常也需要重写 GetHashCode 方法。这是因为.NET 的内置数据结构(如 DictionaryHashSet 等哈希集合)会使用 GetHashCode 方法返回的值来组织数据。如果不重写 GetHashCode 方法,可能会导致哈希集合在查找元素时出现不一致的行为。

实现步骤

1. 理解 EqualsGetHashCode 的关系

如果两个对象根据 Equals 方法判断为相等,那么它们的 GetHashCode 方法必须返回相同的值;但如果两个对象的 GetHashCode 方法返回相同的值,它们不一定相等,此时需要调用 Equals 方法进一步判断。

2. 重写 GetHashCode 方法

  • 简单属性:如果对象只有一个关键属性,可以直接返回该属性的哈希码。
1
2
3
4
5
6
7
8
public class Foo
{
public int FooId { get; set; }
public override int GetHashCode()
{
return FooId;
}
}
  • 多个属性:当需要考虑多个属性时,可以使用一些算法将多个属性的哈希码组合起来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example
{
public int Field1 { get; set; }
public int Field2 { get; set; }
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = (hash * 7) + Field1.GetHashCode();
hash = (hash * 7) + Field2.GetHashCode();
return hash;
}
}
}
  • 使用 HashCode.Combine(C# 8.0 及以后):这是一种更简洁的方式。
1
2
3
4
5
6
7
8
9
public class Example
{
public int Field1 { get; set; }
public int Field2 { get; set; }
public override int GetHashCode()
{
return HashCode.Combine(Field1, Field2);
}
}

3. 重写 Equals 方法

重写 Equals 方法时,需要检查对象是否为 null,并比较对象的类型和属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Foo
{
public int FooId { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
}

核心代码

简单属性的 GetHashCode 实现

1
2
3
4
5
6
7
8
public class SimpleClass
{
public int Id { get; set; }
public override int GetHashCode()
{
return Id;
}
}

多个属性的 GetHashCode 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ComplexClass
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Property1.GetHashCode();
hash = (hash * 23) + (Property2 != null ? Property2.GetHashCode() : 0);
return hash;
}
}
}

使用 HashCode.Combine 的实现

1
2
3
4
5
6
7
8
9
public class ModernClass
{
public int FieldA { get; set; }
public double FieldB { get; set; }
public override int GetHashCode()
{
return HashCode.Combine(FieldA, FieldB);
}
}

最佳实践

  • 保持一致性:确保 GetHashCode 方法使用的属性与 Equals 方法一致。
  • 避免使用可变属性:哈希码在对象的生命周期内应该保持不变,因此避免使用可以被外部修改的属性来计算哈希码。
  • 考虑性能:选择合适的哈希码计算方法,以提高哈希集合的查找性能。

常见问题

不重写 GetHashCode 会怎样?

如果只重写了 Equals 方法而没有重写 GetHashCode 方法,可能会导致哈希集合(如 DictionaryHashSet)在查找元素时出现问题。因为哈希集合会先比较对象的哈希码,如果哈希码不同,就不会调用 Equals 方法进行比较,从而可能导致相等的对象被认为不相等。

哈希码冲突怎么办?

哈希码冲突是不可避免的,因为可能的哈希码数量是有限的(int 类型为 2^32)。一个好的哈希函数应该尽量均匀地分布哈希码,以减少冲突的概率。当发生冲突时,哈希集合会调用 Equals 方法进一步判断对象是否相等。


重写Equals方法时为何要重写GetHashCode方法
https://119291.xyz/posts/importance-of-overriding-gethashcode-when-equals-overridden/
作者
ww
发布于
2025年5月28日
许可协议