0Pricing
C# Academy · 课时

重写 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 实现 IEquatable
  2. 重写 GetHashCode
  3. 实现 IComparable
  4. 使用 IComparer 自定义排序
← 返回 C# Academy