เหตุใดจึงสำคัญที่ต้องแทนที่ GetHashCode เมื่อเมธอด Equals ถูกเขียนทับ


1444

รับคลาสต่อไปนี้

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

ฉันได้เขียนทับEqualsวิธีเพราะFooเป็นตัวแทนของแถวสำหรับFooตาราง s ซึ่งเป็นวิธีที่ต้องการสำหรับการเอาชนะGetHashCode?

ทำไมมันจึงเป็นสิ่งสำคัญที่จะแทนที่GetHashCode?


36
สิ่งสำคัญคือต้องใช้ทั้งสองเท่ากับและ gethashcode เนื่องจากการชนโดยเฉพาะอย่างยิ่งในขณะที่ใช้พจนานุกรม หากวัตถุสองรายการส่งคืนแฮชโค้ดเดียวกันวัตถุเหล่านั้นจะถูกแทรกในพจนานุกรมพร้อมกับการผูกมัด ในขณะที่การเข้าถึงรายการเท่ากับวิธีการที่ใช้
DarthVader

คำตอบ:


1319

ใช่มันเป็นสิ่งสำคัญหากรายการของคุณจะถูกใช้เป็นคีย์ในพจนานุกรมหรือHashSet<T>อื่น ๆ - เนื่องจากมีการใช้ (ในกรณีที่ไม่มีการกำหนดเองIEqualityComparer<T>) เพื่อจัดกลุ่มรายการเป็นที่เก็บข้อมูล หากรหัสแฮชสำหรับสองรายการไม่ตรงกันอาจไม่มีการพิจารณาว่าเท่าเทียมกัน ( เท่ากับจะไม่ถูกเรียก)

กระบวนการGetHashCode ()วิธีควรสะท้อนEqualsตรรกะ กฎคือ:

  • หากสองสิ่งเท่ากัน ( Equals(...) == true) จากนั้นพวกเขาจะต้องส่งกลับค่าเดียวกันสำหรับGetHashCode()
  • หากGetHashCode()เท่ากันก็ไม่จำเป็นสำหรับพวกเขาที่จะเหมือนกัน; นี่คือการชนกันและEqualsจะถูกเรียกให้ดูว่ามันเป็นความเท่าเทียมกันจริงหรือไม่

ในกรณีนี้ดูเหมือนว่า " return FooId;" เป็นการGetHashCode()ใช้งานที่เหมาะสม หากคุณกำลังทดสอบคุณสมบัติหลายรายการเป็นเรื่องปกติที่จะรวมเข้าด้วยกันโดยใช้รหัสเช่นด้านล่างเพื่อลดการชนในแนวทแยง (เช่นเพื่อให้new Foo(3,5)มีรหัสแฮชที่แตกต่างกันไปnew Foo(5,3)):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

โอ้ - เพื่อความสะดวกของคุณอาจยังพิจารณาให้==และ!=ผู้ประกอบการเมื่อเอาชนะและEqualsGetHashCode


การสาธิตของสิ่งที่เกิดขึ้นเมื่อคุณได้รับผิดนี้อยู่ที่นี่


49
ฉันจะถามคุณว่าคุณคูณด้วยปัจจัยดังกล่าวได้ไหม?
Leandro López

22
อันที่จริงฉันอาจสูญเสียหนึ่งในนั้น ประเด็นคือพยายามลดจำนวนการชนให้น้อยที่สุด - เพื่อให้วัตถุ {1,0,0} มีแฮชแตกต่างจาก {0,1,0} และ {0,0,1} (ถ้าคุณเห็นสิ่งที่ฉันหมายถึง ),
Marc Gravell

13
ฉัน tweaked ตัวเลขเพื่อให้ชัดเจน (และเพิ่มเมล็ด) โค้ดบางตัวใช้ตัวเลขที่แตกต่างกัน - ตัวอย่างคอมไพเลอร์ C # (สำหรับประเภทที่ไม่ระบุตัวตน) ใช้เมล็ดของ 0x51ed270b และปัจจัย -1521134295
Marc Gravell

76
@Leandro López: โดยปกติแล้วปัจจัยจะถูกเลือกให้เป็นจำนวนเฉพาะเพราะจะทำให้จำนวนการชนน้อยลง
Andrei Rînea

29
"โอ้ - เพื่อความสะดวกคุณอาจพิจารณาจัดหา == และ! = ตัวดำเนินการเมื่อแทนที่ Equals และ GethashCode": Microsoft ไม่สนับสนุนการใช้ตัวดำเนินการ == สำหรับวัตถุที่ไม่เปลี่ยนรูป - msdn.microsoft.com/en-us/library/ ms173147.aspx - "ไม่ใช่ความคิดที่ดีที่จะแทนที่โอเปอเรเตอร์ == ในประเภทที่ไม่เปลี่ยนรูปแบบ"
antiduh

137

จริงๆแล้วมันยากมากที่จะใช้GetHashCode()อย่างถูกต้องเพราะนอกจากกฎที่ Marc ได้กล่าวมาแล้วรหัสแฮชไม่ควรเปลี่ยนแปลงในช่วงอายุการใช้งานของวัตถุ ดังนั้นฟิลด์ที่ใช้ในการคำนวณรหัสแฮชต้องไม่เปลี่ยนรูป

ในที่สุดฉันก็พบวิธีแก้ปัญหานี้เมื่อฉันทำงานกับ NHibernate แนวทางของฉันคือการคำนวณรหัสแฮชจาก ID ของวัตถุ ID สามารถตั้งค่าได้แม้ว่า Constructor ดังนั้นหากคุณต้องการเปลี่ยน ID ซึ่งไม่น่าเป็นไปได้มากคุณต้องสร้างออบเจ็กต์ใหม่ที่มี ID ใหม่และดังนั้นจึงเป็นรหัสแฮชใหม่ วิธีการนี้ทำงานได้ดีที่สุดกับ GUIDs เนื่องจากคุณสามารถกำหนดตัวสร้างแบบไร้พารามิเตอร์ซึ่งจะสร้าง ID แบบสุ่ม


20
@vanja ฉันเชื่อว่าเกี่ยวข้องกับ: หากคุณเพิ่มวัตถุลงในพจนานุกรมแล้วเปลี่ยนรหัสของวัตถุเมื่อดึงข้อมูลในภายหลังคุณจะใช้แฮชที่แตกต่างกันเพื่อดึงข้อมูลออกมาดังนั้นคุณจะไม่ได้รับมันจากพจนานุกรม
Aneves

74
เอกสารของ Microsoft เกี่ยวกับฟังก์ชัน GetHashCode () ไม่ได้ระบุหรือบอกเป็นนัยว่าแฮชของวัตถุจะต้องสอดคล้องตลอดอายุการใช้งาน ในความเป็นจริงมันจะอธิบายเฉพาะกรณีที่อนุญาตหนึ่งกรณีซึ่งอาจไม่ : "กระบวนการ GetHashCode สำหรับวัตถุต้องส่งคืนรหัสแฮชเดียวกันอย่างสม่ำเสมอตราบใดที่ไม่มีการดัดแปลงสถานะของวัตถุที่กำหนดค่าตอบแทนของวิธีการเท่ากับวัตถุ ."
PeterAllenWebb

37
"รหัสแฮชไม่ควรเปลี่ยนแปลงในช่วงอายุการใช้งานของวัตถุ" - ไม่เป็นความจริง
Apocalypse

7
วิธีที่ดีกว่าที่จะกล่าวคือ "รหัสแฮช (หรือการเปลี่ยนแปลงของค่าเท่ากับ) ควรเปลี่ยนในช่วงเวลาที่วัตถุถูกใช้เป็นกุญแจสำคัญสำหรับคอลเลกชัน" ดังนั้นหากคุณเพิ่มวัตถุลงในพจนานุกรมเป็นคีย์คุณต้องมั่นใจว่า GetHashCode และ Equals จะไม่เปลี่ยนเอาต์พุตสำหรับอินพุตที่กำหนดจนกว่าคุณจะลบวัตถุออกจากพจนานุกรม
Scott Chamberlain

11
@ScottChamberlain ฉันคิดว่าคุณลืมไม่ได้ในความคิดเห็นของคุณมันควรจะเป็น: "รหัสแฮช (หรือการ evaulation of equals) ไม่ควรเปลี่ยนแปลงในช่วงเวลาที่วัตถุถูกใช้เป็นกุญแจสำหรับคอลเลกชัน" ขวา?
Stan Prokop

57

โดยเอาชนะเท่ากับคุณโดยทั่วไปว่าคุณเป็นคนที่รู้วิธีเปรียบเทียบสองประเภทที่กำหนดดังนั้นคุณน่าจะเป็นตัวเลือกที่ดีที่สุดในการให้รหัสแฮชที่ดีที่สุด

นี่คือตัวอย่างของวิธีที่ ReSharper เขียนฟังก์ชัน GetHashCode () ให้คุณ:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

อย่างที่คุณเห็นมันแค่พยายามคาดเดารหัสแฮชที่ดีโดยยึดตามฟิลด์ทั้งหมดในคลาส แต่เนื่องจากคุณรู้ว่าโดเมนหรือช่วงค่าของวัตถุคุณยังคงสามารถให้รหัสที่ดีกว่าได้


7
สิ่งนี้จะไม่ส่งคืนศูนย์หรือไม่ น่าจะเริ่มต้นผลลัพธ์ที่ 1! ยังต้องการเซมิโคลอนอีกไม่กี่ตัว
Sam Mackrill

16
คุณรู้หรือไม่ว่าตัวดำเนินการ XOR (^) ทำอะไร
สตีเฟ่น Drew

1
อย่างที่ฉันพูดนี่คือสิ่งที่ R # เขียนให้คุณ (อย่างน้อยมันก็เป็นสิ่งที่เกิดขึ้นในปี 2008) เมื่อถูกถาม เห็นได้ชัดว่าข้อมูลโค้ดนี้มีวัตถุประสงค์เพื่อปรับแต่งโดยโปรแกรมเมอร์ไม่ทางใดก็ทางหนึ่ง สำหรับเซมิโคลอนที่หายไป ... ใช่ดูเหมือนว่าฉันจะออกไปเมื่อฉันคัดลอกโค้ดจากการเลือกภูมิภาคใน Visual Studio ฉันก็คิดว่าคนจะคิดออกทั้งสองอย่าง
กับดัก

3
@SamMackrill ฉันได้เพิ่มในเซมิโคลอนที่หายไปแล้ว
Matthew Murdoch

5
@SamMackrill ไม่มีก็จะไม่ได้กลับ 0. ดังนั้น0 ^ a = a 0 ^ m_someVar1 = m_someVar1เขาอาจตั้งค่าเริ่มต้นของresultเป็นm_someVar1เช่นกัน
Millie Smith

41

โปรดอย่าลืมตรวจสอบพารามิเตอร์ obj กับเมื่อเอาชนะnull Equals()และยังเปรียบเทียบประเภท

public override bool Equals(object obj)
{
    Foo fooItem = obj as Foo;

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

เหตุผลของเรื่องนี้คือต้องกลับผิดพลาดในการเปรียบเทียบกับEquals nullดูhttp://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


6
การตรวจสอบประเภทนี้จะล้มเหลวในสถานการณ์ที่คลาสย่อยอ้างอิงถึงวิธี Superclass Equals ซึ่งเป็นส่วนหนึ่งของการเปรียบเทียบของตัวเอง (เช่น base.Equals (obj)) - ควรใช้แทน
sweetfa

@sweetfa: ขึ้นอยู่กับวิธีการดำเนินการเท่ากับของคลาสย่อย นอกจากนี้ยังสามารถเรียก base.Equals ((BaseType) obj)) ซึ่งจะทำงานได้ดี
huha

2
ไม่มันจะไม่: msdn.microsoft.com/en-us/library/system.object.gettype.aspx และนอกจากนี้การใช้วิธีการไม่ควรล้มเหลวหรือประสบความสำเร็จขึ้นอยู่กับวิธีการที่เรียกว่า ถ้า runtime-type ของวัตถุนั้นเป็น subclass ของ baseclass บางอันดังนั้น Equals () ของ baseclass ควรจะคืนค่าจริงถ้าobjจริง ๆ แล้วเท่ากับเท่ากับthisว่า Equals () ของ baseclass นั้นถูกเรียกมาอย่างไร
ดาวพฤหัสบดี

2
การย้ายfooItemไปด้านบนแล้วตรวจสอบค่าว่างจะทำงานได้ดีขึ้นในกรณีที่เป็นโมฆะหรือผิดประเภท
IllidanS4 ต้องการโมนิก้ากลับ

1
@ 40Alpha ใช่แล้วobj as Fooจะไม่ถูกต้อง
IllidanS4 ต้องการโมนิก้ากลับ

35

เกี่ยวกับ:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

สมมติว่าประสิทธิภาพไม่ใช่ปัญหา :)


1
erm - แต่คุณกำลังส่งคืนสตริงสำหรับวิธีการอิงตามนั้น _0
jim tollan

32
ไม่เขาเรียกใช้ GetHashCode () จากวัตถุ String ซึ่งส่งกลับค่า int
Richard Clayton

3
ฉันไม่คาดหวังนี้จะเร็วที่สุดเท่าที่ผมจะชอบที่จะไม่เพียง แต่สำหรับมวยที่เกี่ยวข้องสำหรับประเภทค่า string.Formatแต่ยังสำหรับการปฏิบัติงานของ หนึ่ง geeky new { prop1, prop2, prop3 }.GetHashCode()อีกฉันได้เห็นคือ ไม่สามารถแสดงความคิดเห็นได้แม้ว่าจะใช้อันไหนช้ากว่ากันระหว่างสองคนนี้ อย่าใช้เครื่องมือในทางที่ผิด
nawfal

16
นี้จะกลับเป็นจริงสำหรับและ{ prop1="_X", prop2="Y", prop3="Z" } { prop1="", prop2="X_Y", prop3="Z_" }คุณอาจไม่ต้องการมัน
voetsjoeba

2
ใช่คุณสามารถแทนที่สัญลักษณ์ขีดล่างด้วยสิ่งที่ไม่ธรรมดา (เช่น•, ▲, ►, ◄, ☺, ☻) และหวังว่าผู้ใช้ของคุณจะไม่ใช้สัญลักษณ์เหล่านี้ ... :)
Ludmil Tinkov

13

เรามีสองปัญหาที่ต้องรับมือ

  1. คุณไม่สามารถให้เหตุผลได้GetHashCode()หากฟิลด์ใด ๆ ในวัตถุสามารถเปลี่ยนแปลงได้ GetHashCode()ก็มักจะวัตถุจะไม่ถูกนำมาใช้ในคอลเลกชันที่ขึ้นอยู่กับ ดังนั้นค่าใช้จ่ายในการติดตั้งGetHashCode()จึงไม่คุ้มค่าหรือเป็นไปไม่ได้

  2. หากมีคนวางวัตถุของคุณในคอลเล็กชันที่เรียก GetHashCode()และคุณได้แทนที่Equals()โดยไม่ GetHashCode()ทำตัวให้ถูกวิธีบุคคลนั้นอาจใช้เวลาหลายวันในการติดตามปัญหา

ดังนั้นโดยค่าเริ่มต้นฉันทำ

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null)
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

5
การโยนข้อยกเว้นจาก GetHashCode เป็นการละเมิดสัญญา Object ไม่มีความยากลำบากในการกำหนดGetHashCodeฟังก์ชั่นที่วัตถุสองอันใดที่มีค่าเท่ากับการส่งคืนรหัสแฮชเดียวกัน return 24601;และทั้งสองจะมีการใช้งานที่ถูกต้องreturn 8675309; GetHashCodeประสิทธิภาพของDictionaryจะดีเมื่อจำนวนรายการมีขนาดเล็กและจะแย่มากถ้าจำนวนรายการมีขนาดใหญ่ แต่จะทำงานได้อย่างถูกต้องในทุกกรณี
supercat

2
@supercat มันเป็นไปไม่ได้ที่จะใช้ GetHashCode ด้วยวิธีที่เหมาะสมหากฟิลด์ระบุในวัตถุสามารถเปลี่ยนแปลงได้เนื่องจากรหัสแฮชต้องไม่เปลี่ยนแปลง การทำในสิ่งที่คุณพูดอาจทำให้ใครบางคนต้องใช้เวลาหลายวันในการติดตามปัญหาด้านประสิทธิภาพแล้วหลายสัปดาห์ในระบบขนาดใหญ่ที่ออกแบบใหม่เพื่อลบการใช้พจนานุกรม
Ian Ringrose

2
ฉันเคยทำอะไรแบบนี้กับทุกคลาสที่ฉันกำหนดไว้ว่าต้องการ Equals () และที่ฉันแน่ใจว่าฉันไม่เคยใช้วัตถุนั้นเป็นกุญแจในการสะสม แล้ววันหนึ่งโปรแกรมที่ฉันใช้วัตถุแบบนั้นในฐานะอินพุตของตัวควบคุม DevExpress XtraGrid ล้มเหลว มันกลับกลายเป็น XtraGrid ที่อยู่ด้านหลังของฉันกำลังสร้าง HashTable หรืออะไรก็ตามที่อยู่บนวัตถุของฉัน ฉันโต้เถียงเล็กน้อยกับ DevExpress ที่ให้การสนับสนุนผู้คนเกี่ยวกับเรื่องนี้ ฉันบอกว่ามันไม่ฉลาดเลยที่พวกเขาจะใช้ฟังก์ชั่นและความน่าเชื่อถือของส่วนประกอบในการใช้งานลูกค้าที่ไม่รู้จักวิธีการปิดบัง
RenniePet

คน DevExpress ค่อนข้างน่ารำคาญโดยทั่วไปบอกว่าฉันต้องเป็นคนงี่เง่าที่จะแสดงข้อยกเว้นในวิธี GetHashCode () ฉันยังคิดว่าพวกเขาควรหาวิธีทางเลือกในการทำสิ่งที่พวกเขากำลังทำอยู่ - ฉันจำ Marc Gravell ในหัวข้ออื่นที่อธิบายว่าเขาสร้างพจนานุกรมของวัตถุที่ไม่มีกฎเกณฑ์ได้อย่างไรโดยไม่ต้องพึ่งพา GetHashCode () - จำไม่ได้ว่าเขาทำอย่างไร แม้
RenniePet

4
@RenniePet ต้องดีกว่าที่จะต้องสนใจเพราะโยนข้อยกเว้นแล้วมีปัญหาในการค้นหาข้อผิดพลาดเนื่องจากการใช้งานที่ไม่ถูกต้อง
Ian Ringrose

12

เป็นเพราะกรอบต้องการวัตถุที่สองที่เหมือนกันต้องมี hashcode เดียวกัน หากคุณแทนที่เมธอด equals เพื่อทำการเปรียบเทียบพิเศษของสองวัตถุและทั้งสองวัตถุนั้นถือว่าเหมือนกันด้วยวิธีการดังนั้นรหัสแฮชของวัตถุทั้งสองนั้นจะต้องเหมือนกัน (พจนานุกรมและแฮชเทเบิลใช้หลักการนี้)


11

เพียงเพิ่มคำตอบข้างบน:

หากคุณไม่ได้แทนที่เท่ากับแล้วพฤติกรรมเริ่มต้นคือการอ้างอิงของวัตถุที่มีการเปรียบเทียบ เช่นเดียวกับ hashcode - โดยทั่วไปการเริ่มต้นจะอิงตามที่อยู่หน่วยความจำของการอ้างอิง เนื่องจากคุณได้ลบล้าง Equals หมายความว่าพฤติกรรมที่ถูกต้องคือการเปรียบเทียบสิ่งที่คุณใช้กับ Equals ไม่ใช่การอ้างอิงดังนั้นคุณควรทำเช่นเดียวกันกับ hashcode

ลูกค้าในชั้นเรียนของคุณจะคาดหวังว่าแฮชโค้ดจะมีตรรกะที่คล้ายกันกับวิธีเท่ากับเช่นวิธี linq ที่ใช้ IEqualityComparer ก่อนเปรียบเทียบแฮชโค้ดและถ้าพวกเขาเท่ากันพวกเขาจะเปรียบเทียบวิธี Equals () ซึ่งอาจมีราคาแพงกว่า ในการรันหากเราไม่ได้ใช้ hashcode วัตถุที่เท่ากันอาจจะมี hashcodes ที่แตกต่างกัน (เพราะมันมีที่อยู่หน่วยความจำที่แตกต่างกัน) และจะถูกพิจารณาอย่างผิด ๆ ว่าไม่เท่ากับ (เท่ากับ ()

นอกจากนี้ยกเว้นปัญหาที่คุณอาจไม่สามารถค้นหาวัตถุของคุณหากคุณใช้มันในพจนานุกรม (เพราะมันถูกแทรกโดยหนึ่ง hashcode และเมื่อคุณมองหามัน hashcode เริ่มต้นอาจจะแตกต่างกันและอีกครั้งเท่ากับ () จะไม่ถูกเรียกเช่น Marc Gravell อธิบายในคำตอบของเขาคุณยังแนะนำการละเมิดพจนานุกรมหรือแนวคิด hashset ซึ่งไม่ควรอนุญาตให้ใช้รหัสที่เหมือนกัน - คุณได้ประกาศแล้วว่าวัตถุเหล่านั้นจะเหมือนกันเมื่อคุณเอาชนะเท่ากับคุณ ไม่ต้องการให้ทั้งคู่เป็นคีย์ที่แตกต่างกันในโครงสร้างข้อมูลซึ่งสมมติว่ามีคีย์เฉพาะ แต่เนื่องจากมีแฮชโค้ดที่ต่างกันคีย์ "เดียวกัน" จะถูกแทรกเป็นคีย์ที่ต่างกัน


8

รหัสแฮชใช้สำหรับคอลเลกชันที่ใช้แฮชเช่น Dictionary, Hashtable, HashSet เป็นต้นวัตถุประสงค์ของรหัสนี้คือการจัดเรียงวัตถุที่ต้องการล่วงหน้าอย่างรวดเร็วโดยการใส่ลงในกลุ่มเฉพาะ (bucket) การเรียงลำดับล่วงหน้านี้ช่วยอย่างมากในการค้นหาวัตถุนี้เมื่อคุณต้องการเรียกคืนจากแฮชคอลเลกชันเนื่องจากรหัสจะต้องค้นหาวัตถุของคุณในที่เก็บข้อมูลชุดเดียวแทนที่จะอยู่ในที่เก็บทั้งหมด การกระจายรหัสแฮชที่ดีขึ้น ในสถานการณ์ที่เหมาะสมที่แต่ละวัตถุมีรหัสแฮชที่ไม่ซ้ำกันการค้นหาเป็นการดำเนินการ O (1) ในกรณีส่วนใหญ่มันเข้าใกล้ O (1)


7

มันไม่จำเป็นว่าสำคัญ ขึ้นอยู่กับขนาดของคอลเลกชันและข้อกำหนดด้านประสิทธิภาพของคุณและคลาสของคุณจะถูกใช้ในห้องสมุดที่คุณอาจไม่ทราบข้อกำหนดด้านประสิทธิภาพหรือไม่ ฉันรู้ว่าขนาดคอลเล็กชั่นของฉันไม่ใหญ่มากและเวลาของฉันมีค่ามากกว่าประสิทธิภาพสองสามไมโครวินาทีโดยการสร้างรหัสแฮชที่สมบูรณ์แบบ ดังนั้น (เพื่อกำจัดคำเตือนที่น่ารำคาญโดยคอมไพเลอร์) ฉันใช้:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(แน่นอนฉันสามารถใช้ #pragma เพื่อปิดคำเตือนได้ แต่ฉันชอบวิธีนี้)

เมื่อคุณอยู่ในตำแหน่งที่คุณไม่จำเป็นต้องมีประสิทธิภาพกว่าทุกประเด็นที่กล่าวถึงโดยคนอื่น ๆ ที่นี่ใช้ของหลักสูตร สิ่งสำคัญที่สุด - ไม่เช่นนั้นคุณจะได้รับผลที่ผิดเมื่อดึงรายการจากชุดแฮชหรือพจนานุกรม: รหัสแฮชจะต้องไม่เปลี่ยนแปลงตามอายุการใช้งานของวัตถุ (แม่นยำยิ่งขึ้นในช่วงเวลาที่ต้องการรหัสแฮชเช่นในขณะที่ถูก คีย์ในพจนานุกรม): ตัวอย่างเช่นต่อไปนี้ผิดเนื่องจากค่าเป็นแบบพับลิกและสามารถเปลี่ยนเป็นคลาสภายนอกได้ในช่วงเวลาชีวิตของอินสแตนซ์ดังนั้นคุณต้องไม่ใช้เป็นพื้นฐานสำหรับรหัสแฮช:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

ในทางกลับกันหากไม่สามารถเปลี่ยนค่าได้มันก็สามารถใช้งานได้:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }

3
downvoted นี่เป็นสิ่งที่ผิดธรรมดา แม้แต่สถานะของ Microsoft ใน MSDN ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ) ว่าค่าของ GetHashCode ต้องเปลี่ยนเมื่อสถานะวัตถุเปลี่ยนแปลงในลักษณะที่อาจส่งผลกระทบต่อค่าส่งคืนของการโทร เป็น Equals () และแม้แต่ในตัวอย่างมันยังแสดงการใช้งาน GetHashCode ที่ขึ้นอยู่กับค่านิยมที่เปลี่ยนแปลงได้อย่างเต็มที่
เซบาสเตียน PR Gingter

เซบาสเตียนฉันไม่เห็นด้วย: ถ้าคุณเพิ่มวัตถุลงในคอลเลกชันที่ใช้รหัสแฮชมันจะถูกวางในถังขยะขึ้นอยู่กับรหัสแฮช หากคุณเปลี่ยนรหัสแฮชคุณจะไม่พบวัตถุอีกครั้งในคอลเลกชันเนื่องจากจะมีการค้นหาถังขยะที่ไม่ถูกต้อง ในความเป็นจริงนี่คือบางสิ่งที่เกิดขึ้นในรหัสของเราและนั่นเป็นสาเหตุที่ฉันพบว่าจำเป็นต้องชี้ให้เห็น
ILoveFortran

2
เซบาสเตียนนอกจากนี้ฉันไม่เห็นคำสั่งในลิงก์ ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ) ที่ GetHashCode () ต้องเปลี่ยน ในทางตรงกันข้าม - จะต้องไม่เปลี่ยนแปลงตราบใดที่ Equals ส่งคืนค่าเดียวกันสำหรับอาร์กิวเมนต์เดียวกัน: "เมธอด GetHashCode สำหรับวัตถุจะต้องส่งคืนรหัสแฮชเดียวกันอย่างสม่ำเสมอตราบใดที่ไม่มีการแก้ไขสถานะของวัตถุที่กำหนดค่าส่งคืน ของวิธี Equals ของวัตถุ "คำสั่งนี้ไม่ได้บอกเป็นนัยตรงข้ามว่าจะต้องเปลี่ยนแปลงหากค่าส่งคืนสำหรับการเปลี่ยนแปลงเท่ากับ
ILoveFortran

2
@ โจวคุณสับสนลูกค้า / ผู้บริโภคด้านสัญญากับผู้ผลิต / ผู้ดำเนินการ ฉันกำลังพูดถึงความรับผิดชอบของผู้ดำเนินการที่แทนที่ GetHashCode () คุณกำลังพูดถึงผู้บริโภคซึ่งเป็นผู้ใช้ค่า
ILoveFortran

1
ความเข้าใจผิดที่สมบูรณ์ ... :) ความจริงก็คือรหัสแฮชต้องเปลี่ยนเมื่อสถานะของวัตถุเปลี่ยนแปลงเว้นแต่สถานะนั้นไม่เกี่ยวข้องกับตัวตนของวัตถุ นอกจากนี้คุณไม่ควรใช้วัตถุ MUTABLE เป็นกุญแจในคอลเลกชันของคุณ ใช้วัตถุแบบอ่านอย่างเดียวเพื่อจุดประสงค์นี้ GetHashCode, เท่ากับ ... และวิธีการอื่น ๆ ที่มีชื่อฉันจำไม่ได้ในขณะนี้ไม่ควรโยน
darlove

0

คุณควรรับประกันเสมอว่าหากวัตถุสองชิ้นมีค่าเท่ากันตามที่กำหนดโดย Equals () วัตถุเหล่านั้นควรส่งคืนรหัสแฮชเดียวกัน ในฐานะที่เป็นความคิดเห็นอื่น ๆ ของรัฐในทางทฤษฎีสิ่งนี้ไม่จำเป็นถ้าวัตถุจะไม่ถูกนำมาใช้ในภาชนะที่ใช้กัญชาเช่น HashSet หรือพจนานุกรม ฉันจะแนะนำให้คุณทำตามกฎนี้เสมอ เหตุผลก็คือเพราะมันง่ายเกินไปสำหรับคนที่จะเปลี่ยนคอลเลกชันจากประเภทหนึ่งไปยังอีกประเภทหนึ่งโดยมีเจตนาที่ดีในการปรับปรุงประสิทธิภาพหรือเพียงแค่ถ่ายทอดความหมายของรหัสในวิธีที่ดีกว่า

ตัวอย่างเช่นสมมติว่าเราเก็บวัตถุบางอย่างในรายการ บางครั้งในภายหลังมีคนรู้ตัวว่า HashSet เป็นทางเลือกที่ดีกว่าเนื่องจากมีลักษณะการค้นหาที่ดีกว่า นี่คือเมื่อเราสามารถมีปัญหา รายการภายในจะใช้ตัวเปรียบเทียบความเท่าเทียมกันเริ่มต้นสำหรับประเภทซึ่งหมายถึงเท่ากับในกรณีของคุณในขณะที่ HashSet ใช้ GetHashCode () หากทั้งสองทำงานแตกต่างกันโปรแกรมของคุณก็จะเหมือนกัน และจำไว้ว่าปัญหาดังกล่าวไม่ใช่วิธีที่ง่ายที่สุดในการแก้ไขปัญหา

ฉันได้สรุปพฤติกรรมนี้กับข้อผิดพลาด GetHashCode () อื่น ๆ ในบล็อกโพสต์ที่คุณสามารถค้นหาตัวอย่างและคำอธิบายเพิ่มเติมได้


0

ตาม.NET 4.7วิธีที่ต้องการในการเอาชนะGetHashCode()แสดงไว้ด้านล่าง หากกำหนดเป้าหมายเวอร์ชัน. NET ที่เก่ากว่าให้รวมแพ็คเกจSystem.ValueTuple nuget

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

ในแง่ของประสิทธิภาพวิธีนี้จะมีประสิทธิภาพสูงกว่าการใช้รหัสแฮชคอมโพสิตส่วนใหญ่ ValueTupleเป็นstructดังนั้นจะไม่มีขยะใด ๆ และอัลกอริทึมพื้นฐานคือให้เร็วที่สุดเท่าที่จะได้รับ


-1

ฉันเข้าใจว่า GetHashCode ดั้งเดิม () ส่งคืนที่อยู่หน่วยความจำของวัตถุดังนั้นจึงจำเป็นที่จะต้องแทนที่หากคุณต้องการเปรียบเทียบสองวัตถุที่แตกต่างกัน

แก้ไข: ไม่ถูกต้องวิธีการ GetHashCode () เดิมไม่สามารถรับประกันความเท่าเทียมกันของ 2 ค่า แม้ว่าวัตถุที่เท่ากันจะส่งคืนรหัสแฮชเดียวกัน


-6

การใช้ภาพสะท้อนด้านล่างนี้เป็นตัวเลือกที่ดีกว่าเมื่อพิจารณาคุณสมบัติสาธารณะเช่นเดียวกับที่คุณไม่ต้องกังวลเกี่ยวกับการเพิ่ม / ลบคุณสมบัติ (แม้ว่าจะไม่ใช่สถานการณ์ทั่วไป) ฉันพบว่ามันทำงานได้ดีขึ้นด้วย (เปรียบเทียบเวลาโดยใช้ Diagonistics stop watch)

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }

12
การใช้งานของ GetHashCode () คาดว่าจะมีน้ำหนักเบามาก ฉันไม่แน่ใจว่าการใช้การสะท้อนนั้นเห็นได้ชัดเจนกับ StopWatch ในการโทรหลายพันครั้ง แต่แน่นอนว่ามันเป็นเรื่องของคนนับล้าน
bohdan_trotsenko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.