0Pricing
C# Academy · บทเรียน

การเขียนทับ GetHashCode

รักษาให้การตรวจสอบความเท่ากันและการทำแฮชสอดคล้องกัน

การเขียนทับ GetHashCode เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน 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 จึงต้องใช้สิ่งนี้

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());
    }
}

การจัดการฟิลด์ที่เป็นค่าว่าง

เมื่อฟิลด์อาจเป็นค่าว่าง HashCode.Combine จะจัดการให้ได้อย่างปลอดภัย หากคำนวณด้วยตนเอง ให้ตรวจสอบค่าว่างเพื่อหลีกเลี่ยง 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)]);
    }
}

ลองทำด้วยตนเอง

พิสูจน์ว่าข้อตกลงนี้เป็นจริง: สร้างคีย์สองรายการที่เท่ากันและยืนยันว่าทั้งการตรวจสอบความเท่ากันและรหัสแฮชของทั้งสองรายการตรงกัน จากนั้นนำไปใช้ใน set

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));
    }
}

ตรวจสอบอย่างรวดเร็ว

ทบทวนข้อตกลงของการทำแฮช

สรุปทบทวน

การทำแฮชที่ถูกต้องช่วยให้คอลเลกชันที่ใช้แฮชทำงานได้

  • ออบเจ็กต์ที่เท่ากันต้องส่งคืนรหัสแฮชเดียวกัน
  • ใช้ HashCode.Combine กับฟิลด์ชุดเดียวกับที่ใช้ใน Equals
  • ทำแฮชจากฟิลด์ที่ไม่เปลี่ยนแปลง เพื่อให้ยังค้นหาคีย์พบ
  • การชนกันเป็นเรื่องปกติ และ Equals จะจัดการกับการชนกันเหล่านั้น
  • ระเบียนจะสร้างการนำไปใช้ที่ถูกต้องให้โดยอัตโนมัติ

คำถามที่พบบ่อย

บทเรียน “การเขียนทับ GetHashCode” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเขียนทับ GetHashCode” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเขียนทับ GetHashCode”

รักษาให้การตรวจสอบความเท่ากันและการทำแฮชสอดคล้องกัน คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การเขียนทับ GetHashCode” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การนำ IEquatable ไปใช้
  2. การเขียนทับ GetHashCode
  3. การนำ IComparable ไปใช้
  4. IComparer สำหรับการเรียงลำดับแบบกำหนดเอง
← กลับไปที่ C# Academy