การใช้ฟิลด์ของวัตถุเป็นคีย์พจนานุกรมทั่วไป


120

ถ้าฉันต้องการใช้ออบเจ็กต์เป็นคีย์สำหรับ a Dictionaryฉันจะต้องใช้วิธีการใดในการลบล้างเพื่อเปรียบเทียบในลักษณะเฉพาะ

สมมติว่าฉันมีคลาสที่มีคุณสมบัติ:

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

และฉันต้องการสร้าง:

Dictionary<Foo, List<Stuff>>

ฉันต้องการให้Fooวัตถุที่มีสิ่งเดียวกันFooIDถือเป็นกลุ่มเดียวกัน วิธีใดที่ฉันจะต้องแทนที่ในFooชั้นเรียน

สรุป: ฉันต้องการจัดประเภทStuffวัตถุเป็นรายการโดยจัดกลุ่มตามFooวัตถุ Stuffออบเจ็กต์จะมีFooIDลิงค์เข้ากับหมวดหมู่

คำตอบ:


151

โดยค่าเริ่มต้นทั้งสองวิธีมีความสำคัญและGetHashCode() Equals()สิ่งสำคัญคือถ้าสองสิ่งเท่ากัน ( Equals()คืนค่าจริง) แสดงว่ามีรหัสแฮชเหมือนกัน ตัวอย่างเช่นคุณอาจ "คืนค่า FooID" ตามที่GetHashCode()คุณต้องการในการแข่งขัน คุณยังสามารถใช้งานIEquatable<Foo>ได้ แต่เป็นทางเลือก:

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

ในที่สุดทางเลือกอื่นก็คือให้IEqualityComparer<T>ทำเช่นเดียวกัน


5
+1 และฉันไม่ได้ตั้งใจจะจี้หัวข้อนี้ แต่ฉันรู้สึกว่า GetHashCode () ควรส่งคืน FooId.GetHashCode () นี่ไม่ใช่รูปแบบที่ถูกต้องหรือไม่?
Ken Browning

8
@ เคน - ก็แค่ต้องส่งคืน int ที่ให้คุณสมบัติที่จำเป็น FooID ใดจะทำเช่นเดียวกับ FooID.GetHashCode () ตามรายละเอียดการใช้งาน Int32.GetHashCode () คือ "ส่งคืนสิ่งนี้" สำหรับประเภทอื่น ๆ (สตริง ฯลฯ ) ใช่: .GetHashCode () จะมีประโยชน์มาก
Marc Gravell

2
ขอบคุณ! ฉันใช้ IEqualityComparer <T> เนื่องจากเป็นเพียง Dicarionary ที่ฉันต้องการวิธีการ overriden
Dana

1
คุณควรทราบว่าประสิทธิภาพของคอนเทนเนอร์ที่ใช้แฮชแท็บ (Dictionary <T>, Dictionary, HashTable ฯลฯ ) ขึ้นอยู่กับคุณภาพของฟังก์ชันแฮชที่ใช้ หากคุณเพียงแค่ FooID เป็นรหัสแฮชคอนเทนเนอร์อาจทำงานได้ไม่ดีนัก
Jørgen Fogh

2
@ JørgenFoghฉันรู้มากว่า; ตัวอย่างที่นำเสนอสอดคล้องกับเจตนาที่ระบุไว้ มีข้อกังวลมากมายที่เกี่ยวข้องกับการไม่เปลี่ยนรูปของแฮช รหัสมีการเปลี่ยนแปลงน้อยกว่าชื่อและโดยปกติจะไม่ซ้ำกันและเป็นตัวบ่งชี้ความเท่าเทียมกัน แม้ว่าเรื่องที่ไม่สำคัญ
Marc Gravell

33

ตามที่คุณต้องการFooIDให้เป็นตัวระบุสำหรับกลุ่มคุณควรใช้เป็นคีย์ในพจนานุกรมแทนอ็อบเจกต์ Foo:

Dictionary<int, List<Stuff>>

หากคุณจะใช้Fooออบเจ็กต์เป็นคีย์คุณก็แค่ใช้GetHashCodeand Equalsmethod เพื่อพิจารณาFooIDคุณสมบัติเท่านั้น Nameคุณสมบัติก็จะเป็นน้ำหนักตายเท่าที่Dictionaryเป็นห่วงดังนั้นคุณก็จะใช้เป็นเสื้อคลุมสำหรับFooint

ดังนั้นจึงเป็นการดีกว่าที่จะใช้FooIDค่านี้โดยตรงจากนั้นคุณไม่จำเป็นต้องใช้อะไรเลยเนื่องจากสิ่งที่Dictionaryรองรับอยู่แล้วโดยใช้intเป็นคีย์

แก้ไข:
หากคุณต้องการใช้Fooคลาสเป็นคีย์ต่อไปการIEqualityComparer<Foo>ใช้งานทำได้ง่าย:

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

การใช้งาน:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());

1
ถูกต้องมากขึ้น int สนับสนุนวิธีการ / อินเทอร์เฟซที่จำเป็นสำหรับการใช้เป็นคีย์แล้ว พจนานุกรมไม่มีความรู้โดยตรงเกี่ยวกับ int หรือประเภทอื่น ๆ
Jim Mischel

ฉันคิดเกี่ยวกับเรื่องนี้ แต่ด้วยเหตุผลหลายประการการใช้วัตถุเป็นปุ่มพจนานุกรมจึงสะอาดและสะดวกกว่า
Dana

1
ดูเหมือนว่าคุณกำลังใช้ออบเจ็กต์เป็นคีย์เท่านั้นเนื่องจากคุณใช้ id เป็นคีย์เท่านั้น
Guffa

8

สำหรับ Foo คุณจะต้องแทนที่ object.GetHashCode () และ object.Equals ()

พจนานุกรมจะเรียก GetHashCode () เพื่อคำนวณที่เก็บแฮชสำหรับแต่ละค่าและเท่ากับเพื่อเปรียบเทียบว่า Foo สองค่าเหมือนกันหรือไม่

อย่าลืมคำนวณรหัสแฮชที่ดี (หลีกเลี่ยงออบเจ็กต์ Foo จำนวนมากที่มีแฮชโค้ดเดียวกัน) แต่ตรวจสอบให้แน่ใจว่า Foos สองตัวที่เท่ากับมีรหัสแฮชเดียวกัน คุณอาจต้องการเริ่มต้นด้วย Equals-Method จากนั้น (ใน GetHashCode ()) x หรือรหัสแฮชของสมาชิกทุกคนที่คุณเปรียบเทียบใน Equals

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

2
กัน - แต่ xor (^) สร้างตัวผสมที่ไม่ดีสำหรับรหัสแฮชเนื่องจากมักทำให้เกิดการชนกันในแนวทแยงบ่อยมาก (เช่น {"foo", "bar"} เทียบกับ {"bar", "foo"} ซึ่งดีกว่า ทางเลือกคือการคูณและเพิ่มแต่ละเทอม - เช่น 17 * a.GetHashCode () + B.GetHashCode ();
Marc Gravell

2
มาร์คฉันเข้าใจว่าคุณหมายถึงอะไร แต่คุณจะได้รับเวทมนตร์หมายเลข 17 ได้อย่างไร? การใช้จำนวนเฉพาะเป็นตัวคูณในการรวมแฮชเป็นประโยชน์หรือไม่? ถ้าเป็นเช่นนั้นทำไม?
เทียว 42

ฉันขอแนะนำให้ส่งคืน: (A + B) .GetHashCode () แทนที่จะเป็น: 17 * A.GetHashCode () + B.GetHashCode () สิ่งนี้จะ: 1) มีโอกาสน้อยที่จะเกิดการชนกันและ 2) ตรวจสอบให้แน่ใจว่าไม่มี จำนวนเต็มล้น
Charles Burns

(A + B) .GetHashCode () สร้างอัลกอริธึมการแฮชที่ไม่ดีมากเนื่องจากชุด (A, B) ที่แตกต่างกันอาจส่งผลให้เกิดแฮชเดียวกันได้หากมีการเชื่อมต่อกับสตริงเดียวกัน "hellow" + "ned" เหมือนกับ "hell" + "owned" และจะส่งผลให้เกิดแฮชเดียวกัน
kaesve

@kaesve แล้ว (A + "" + B) .GetHashCode ()?
Timeless

1

แล้วHashtableชั้นล่ะ!

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Do your job
}

ด้วยวิธีข้างต้นคุณสามารถใช้วัตถุใด ๆ (วัตถุคลาสของคุณ) เป็นคีย์พจนานุกรมทั่วไป :)


1

ผมมีปัญหาเหมือนกัน. ตอนนี้ฉันสามารถใช้วัตถุใด ๆ ที่ฉันพยายามเป็นคีย์ได้เนื่องจากการลบล้าง Equals และ GetHashCode

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

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

นี่คือวิธีที่ใช้ในชั้นเรียน:

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.