เนื่องจากฉันไม่พบคำตอบที่อธิบายว่าทำไมเราจึงควรแทนที่GetHashCode
และEquals
โครงสร้างที่กำหนดเองและทำไมการปรับใช้เริ่มต้น "ไม่น่าจะเหมาะสำหรับใช้เป็นกุญแจในตารางแฮช" ฉันจะทิ้งลิงค์ไปยังบล็อกนี้ โพสต์ซึ่งอธิบายว่าทำไมด้วยตัวอย่างจริงของปัญหาที่เกิดขึ้น
ฉันขอแนะนำให้อ่านโพสต์ทั้งหมด แต่นี่เป็นบทสรุป (เพิ่มการเน้นและการชี้แจง)
เหตุผลที่แฮชเริ่มต้นสำหรับ structs ช้าและไม่ดีมาก:
วิธีที่ CLR ได้รับการออกแบบทุกการโทรไปยังสมาชิกที่กำหนดไว้ในSystem.ValueType
หรือSystem.Enum
ประเภท [อาจ] ทำให้เกิดการจัดสรรมวย [... ]
ตัวดำเนินการของฟังก์ชันแฮชเผชิญภาวะที่กลืนไม่เข้าคายไม่ออก: ทำการกระจายที่ดีของฟังก์ชั่นแฮชหรือทำให้มันรวดเร็ว ในบางกรณีก็เป็นไปได้เพื่อให้บรรลุพวกเขาทั้งสอง แต่มันก็เป็นเรื่องยากที่จะทำเช่นนี้โดยทั่วไปValueType.GetHashCode
ใน
ฟังก์ชันแฮชแบบบัญญัติของโครงสร้าง "รวม" รหัสแฮชของฟิลด์ทั้งหมด แต่วิธีเดียวที่จะได้รับรหัสกัญชาของสนามในValueType
วิธีการคือการใช้สะท้อน ดังนั้นผู้เขียน CLR จึงตัดสินใจแลกเปลี่ยนความเร็วในการแจกจ่ายและGetHashCode
รุ่นเริ่มต้นจะส่งคืนรหัสแฮชของเขตข้อมูลที่ไม่ใช่ศูนย์แรกและ "munges" ด้วยรหัสประเภท [... ] นี่เป็นพฤติกรรมที่สมเหตุสมผลเว้นแต่จะไม่ใช่ . ตัวอย่างเช่นหากคุณโชคไม่ดีพอและฟิลด์แรกของ struct ของคุณมีค่าเท่ากันสำหรับอินสแตนซ์ส่วนใหญ่แล้วฟังก์ชันแฮชจะให้ผลลัพธ์เดียวกันตลอดเวลา และอย่างที่คุณอาจจินตนาการว่าสิ่งนี้จะส่งผลกระทบอย่างมากต่อประสิทธิภาพการทำงานหากอินสแตนซ์เหล่านี้ถูกเก็บไว้ในชุดแฮชหรือตารางแฮช
[ ... ] การดำเนินการสะท้อนตามช้า ช้ามาก.
[... ] ทั้งคู่ValueType.Equals
และValueType.GetHashCode
มีการเพิ่มประสิทธิภาพพิเศษ ถ้าเป็นชนิดที่ไม่ได้มี "ชี้" และเต็มไปอย่างถูกต้อง [ ... ] แล้วรุ่นที่เหมาะสมมากขึ้นมีการใช้GetHashCode
iterates กว่าตัวอย่างและ XORs บล็อคของ 4 ไบต์และวิธีการเปรียบเทียบสองกรณีใช้Equals
memcmp
[... ] แต่การเพิ่มประสิทธิภาพนั้นยุ่งยากมาก อันดับแรกเป็นเรื่องยากที่จะทราบเมื่อเปิดใช้งานการเพิ่มประสิทธิภาพ [... ] ประการที่สองการเปรียบเทียบหน่วยความจำจะไม่จำเป็นต้องให้ผลลัพธ์ที่ถูกต้องแก่คุณ นี่คือตัวอย่างง่ายๆ: [... ] -0.0
และ+0.0
เท่ากัน แต่มีการแทนเลขฐานสองที่แตกต่างกัน
ปัญหาโลกแห่งความจริงที่อธิบายไว้ในโพสต์:
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
เราใช้ tuple ที่มีโครงสร้างแบบกำหนดเองพร้อมการนำความเท่าเทียมกันเริ่มต้นมาใช้ และโชคไม่ดีที่ struct มีสนามแรกที่เป็นตัวเลือกที่ได้รับมักจะเท่ากับ [สตริงว่าง] ประสิทธิภาพการทำงานก็โอเคจนกระทั่งจำนวนองค์ประกอบในชุดเพิ่มขึ้นอย่างมากทำให้เกิดปัญหาประสิทธิภาพจริงใช้เวลาไม่กี่นาทีในการเริ่มต้นการรวบรวมด้วยรายการนับหมื่น
ดังนั้นเพื่อตอบคำถาม "ในกรณีใดฉันควรแพ็คของตัวเองและในกรณีใดที่ฉันสามารถพึ่งพาการใช้งานเริ่มต้นได้อย่างปลอดภัย" อย่างน้อยในกรณีของstructsคุณควรลบล้างEquals
และGetHashCode
เมื่อใดก็ตามที่โครงสร้างแบบกำหนดเองของคุณอาจใช้เป็น Dictionary
ที่สำคัญในตารางแฮชหรือ
ฉันขอแนะนำให้ใช้งานIEquatable<T>
ในกรณีนี้เพื่อหลีกเลี่ยงการชกมวย
ดังที่คำตอบอื่น ๆ บอกไว้ถ้าคุณเขียนคลาสแฮชเริ่มต้นที่ใช้ความเท่าเทียมกันของการอ้างอิงมักจะไม่เป็นไรดังนั้นฉันจะไม่รบกวนในกรณีนี้เว้นแต่คุณจะต้องลบล้างEquals
(ดังนั้นคุณจะต้องแก้ไขให้ถูกต้องGetHashCode
)