การเขียนทับ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การนำ IEquatable ไปใช้
- การเขียนทับ GetHashCode
- การนำ IComparable ไปใช้
- IComparer สำหรับการเรียงลำดับแบบกำหนดเอง