重写 GetHashCode
保持相等性比较与哈希计算的一致。
重写 GetHashCode 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
为什么 GetHashCode 很重要
基于哈希的集合(如 Dictionary 和 HashSet)使用 GetHashCode 为项目分配存储桶。如果重写了 Equals 却没有重写 GetHashCode,这些集合可能无法找到相等的项目。
黄金法则
规则很简单:如果两个对象相等,它们必须返回相同的哈希代码。反过来则不作此要求;不同的对象可以共享同一个哈希代码(即发生冲突)。
using System;
public struct Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public override bool Equals(object obj) => obj is Point p && p.X == X && p.Y == Y;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.GetHashCode() == b.GetHashCode());
}
}使用 HashCode.Combine
HashCode.Combine 辅助方法会将多个字段值混合成分布良好的哈希值。这是实现 GetHashCode 时推荐的现代方式。
using System;
public struct Color
{
public int R, G, B;
public Color(int r, int g, int b) { R = r; G = g; B = b; }
public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
public override int GetHashCode() => HashCode.Combine(R, G, B);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Color(255, 0, 0).GetHashCode() == new Color(255, 0, 0).GetHashCode());
}
}同时实现相等性和哈希
始终成对重写 Equals 和 GetHashCode,并在两者中使用相同的字段。使用不同的字段会导致相等对象生成不同的哈希值。
using System;
public class Book
{
public string Title;
public int Year;
public Book(string title, int year) { Title = title; Year = year; }
public override bool Equals(object obj)
=> obj is Book b && b.Title == Title && b.Year == Year;
public override int GetHashCode() => HashCode.Combine(Title, Year);
}
public class Program
{
public static void Main()
{
var a = new Book("C#", 2020);
var b = new Book("C#", 2020);
Console.WriteLine(a.GetHashCode() == b.GetHashCode());
}
}字典为什么需要它
Dictionary 首先对键进行哈希处理以找到存储桶,然后在该存储桶中使用 Equals。错误的哈希值会将查找引向错误的存储桶,从而永远找不到该键。
using System;
using System.Collections.Generic;
public struct Coord
{
public int X, Y;
public Coord(int x, int y) { X = x; Y = y; }
public override bool Equals(object obj) => obj is Coord c && c.X == X && c.Y == Y;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var map = new Dictionary<Coord, string> { [new Coord(2, 3)] = "hit" };
Console.WriteLine(map[new Coord(2, 3)]);
}
}对不可变字段进行哈希
对象存放在哈希集合中期间,其哈希代码应保持稳定。请根据不可变字段计算哈希值;如果键在插入后发生变化,集合可能会无法找到它。
using System;
public class Account
{
public readonly int Id; // immutable, safe to hash
public string Nickname; // mutable, do not hash
public Account(int id, string nick) { Id = id; Nickname = nick; }
public override bool Equals(object obj) => obj is Account a && a.Id == Id;
public override int GetHashCode() => Id.GetHashCode();
}
public class Program
{
public static void Main()
{
var acc = new Account(42, "old");
int h1 = acc.GetHashCode();
acc.Nickname = "new"; // does not change the hash
Console.WriteLine(h1 == acc.GetHashCode());
}
}处理空字段
当字段可能为 null 时,HashCode.Combine 会安全地处理这种情况。如果您手动计算,请防范 null,以避免 NullReferenceException。
using System;
public class Tag
{
public string Name;
public Tag(string name) { Name = name; }
public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
public override int GetHashCode() => HashCode.Combine(Name); // null-safe
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Tag(null).GetHashCode() == new Tag(null).GetHashCode());
}
}冲突是正常现象
哈希代码是 32 位的,因此对于大型数据集来说,冲突不可避免。好的哈希算法只需将值分散开来,使冲突很少发生;Equals 会解决实际发生的冲突。
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var set = new HashSet<int>();
for (int i = 0; i < 5; i++) set.Add(i);
// Equality still works perfectly even though hashing is imperfect in general
Console.WriteLine(set.Contains(3));
Console.WriteLine(set.Contains(99));
}
}记录类型会为您生成
record 会根据其属性自动生成正确的 GetHashCode,并与其基于值的 Equals 保持一致。这是正确实现哈希的最简单方式。
using System;
using System.Collections.Generic;
public record Coord(int X, int Y);
public class Program
{
public static void Main()
{
var set = new HashSet<Coord> { new Coord(1, 1) };
Console.WriteLine(set.Contains(new Coord(1, 1)));
}
}正确的可相等类型
综合起来:在 Equals 和 GetHashCode 中使用相同的字段、使用不可变的键,并使用 HashCode.Combine 进行混合。这样的类型可以完美地作为字典键使用。
using System;
using System.Collections.Generic;
public struct ProductKey
{
public string Sku;
public int Variant;
public ProductKey(string sku, int variant) { Sku = sku; Variant = variant; }
public override bool Equals(object obj)
=> obj is ProductKey k && k.Sku == Sku && k.Variant == Variant;
public override int GetHashCode() => HashCode.Combine(Sku, Variant);
}
public class Program
{
public static void Main()
{
var stock = new Dictionary<ProductKey, int>
{
[new ProductKey("ABC", 1)] = 10
};
Console.WriteLine(stock[new ProductKey("ABC", 1)]);
}
}亲自试一试
验证这条规则确实成立:创建两个相等的键,确认它们的相等性和哈希代码都相同,然后将它们用于集合。
using System;
using System.Collections.Generic;
public struct Name
{
public string First, Last;
public Name(string first, string last) { First = first; Last = last; }
public override bool Equals(object obj) => obj is Name n && n.First == First && n.Last == Last;
public override int GetHashCode() => HashCode.Combine(First, Last);
}
public class Program
{
public static void Main()
{
var a = new Name("Ada", "Lovelace");
var b = new Name("Ada", "Lovelace");
Console.WriteLine(a.Equals(b) + " " + (a.GetHashCode() == b.GetHashCode()));
var people = new HashSet<Name> { a };
Console.WriteLine(people.Contains(b));
}
}快速检查
回忆哈希规则。
回顾
正确的哈希实现可以确保基于哈希的集合正常工作。
- 相等对象必须返回相同的哈希代码。
- 对与
Equals相同的字段使用HashCode.Combine。 - 对不可变字段进行哈希,以确保键始终可查找。
- 冲突是正常现象;
Equals会解决冲突。 - 记录类型会自动生成正确的实现。
常见问题解答
「重写 GetHashCode」课时是免费的吗?
是的 — 「重写 GetHashCode」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「重写 GetHashCode」这节课中我会学到什么?
保持相等性比较与哈希计算的一致。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「重写 GetHashCode」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 实现 IEquatable
- 重写 GetHashCode
- 实现 IComparable
- 使用 IComparer 自定义排序