Sử dụng trường của một đối tượng làm khóa Từ điển chung


120

Nếu tôi muốn sử dụng các đối tượng làm khóa cho a Dictionary, tôi sẽ cần ghi đè phương thức nào để so sánh chúng theo một cách cụ thể?

Giả sử tôi có một lớp aa có các thuộc tính:

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

    // elided
} 

Và tôi muốn tạo:

Dictionary<Foo, List<Stuff>>

Tôi muốn Foocác đối tượng giống nhau FooIDđược coi là cùng một nhóm. Tôi sẽ cần ghi đè những phương thức nào trong Foolớp?

Tóm lại: Tôi muốn phân loại Stuffcác đối tượng thành danh sách, nhóm theo Foođối tượng. Stuffcác đối tượng sẽ phải FooIDliên kết chúng với danh mục của chúng.

Câu trả lời:


151

Theo mặc định, hai phương thức quan trọng là GetHashCode()Equals(). Điều quan trọng là nếu hai thứ bằng nhau ( Equals()trả về true) thì chúng có cùng mã băm. Ví dụ: bạn có thể "trả về FooID;" như GetHashCode()nếu bạn muốn điều đó như trận đấu. Bạn cũng có thể triển khai IEquatable<Foo>, nhưng đó là tùy chọn:

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

Cuối cùng, một giải pháp thay thế khác là cung cấp IEqualityComparer<T>quyền làm tương tự.


5
+1 và tôi không có ý chiếm đoạt luồng này nhưng tôi có ấn tượng rằng GetHashCode () sẽ trả về FooId.GetHashCode (). Đây không phải là mô hình phù hợp?
Ken Browning

8
@Ken - tốt, nó chỉ cần trả về một int cung cấp các tính năng cần thiết. FooID nào cũng sẽ hoạt động tốt như FooID.GetHashCode (). Như một chi tiết triển khai, Int32.GetHashCode () là "return this;". Đối với các kiểu khác (chuỗi, v.v.), thì có: .GetHashCode () sẽ rất hữu ích.
Marc Gravell

2
Cảm ơn! Tôi đã sử dụng IEqualityComparer <T> vì nó chỉ dành cho Bộ di động mà tôi cần các phương thức ghi đè.
Dana

1
Bạn nên biết rằng hiệu suất của các vùng chứa dựa trên các hashtable (Từ điển <T>, Dictionary, HashTable, v.v.) phụ thuộc vào chất lượng của hàm băm được sử dụng. Nếu bạn chỉ FooID dưới dạng mã băm, các vùng chứa có thể hoạt động rất kém.
Jørgen Fogh

2
@ JørgenFogh Tôi rất biết điều đó; ví dụ được trình bày phù hợp với mục đích đã nêu. Có rất nhiều mối quan tâm liên quan đến tính bất biến của hàm băm; id ít thay đổi hơn tên, và thường là duy nhất đáng tin cậy và các chỉ báo về sự tương đương. Tuy nhiên, một chủ đề không tầm thường.
Marc Gravell

33

Khi bạn muốn FooIDlà định danh cho nhóm, bạn nên sử dụng khóa đó làm khóa trong từ điển thay vì đối tượng Foo:

Dictionary<int, List<Stuff>>

Nếu bạn sử dụng Foođối tượng làm khóa, bạn sẽ chỉ triển khai phương thức GetHashCodeEqualsđể chỉ xem xét thuộc FooIDtính. Các Namebất động sản sẽ chỉ được trọng lượng chết như xa như các Dictionarylo ngại, do đó bạn sẽ chỉ cần sử dụng Foonhư là một wrapper cho một int.

Do đó, tốt hơn là sử dụng FooIDgiá trị trực tiếp và sau đó bạn không phải triển khai bất kỳ thứ gì vì giá trị Dictionaryđã hỗ trợ bằng cách sử dụng intlàm khóa.

Chỉnh sửa:
Nếu bạn vẫn muốn sử dụng Foolớp làm khóa, IEqualityComparer<Foo>cách thực hiện rất dễ dàng:

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

Sử dụng:

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

1
Chính xác hơn, int đã hỗ trợ các phương thức / giao diện cần thiết để nó được sử dụng làm khóa. Từ điển không có kiến ​​thức trực tiếp về int hoặc bất kỳ loại nào khác.
Jim Mischel

Tôi đã nghĩ về điều đó, nhưng vì nhiều lý do, việc sử dụng các đối tượng làm khóa từ điển sẽ gọn gàng và thuận tiện hơn.
Dana

1
Chà, có vẻ như bạn đang sử dụng đối tượng làm khóa, vì bạn thực sự chỉ sử dụng id làm khóa.
Guffa

8

Đối với Foo, bạn sẽ cần ghi đè đối tượng.GetHashCode () và đối tượng.Equals ()

Từ điển sẽ gọi GetHashCode () để tính toán nhóm băm cho mỗi giá trị và Equals để so sánh xem hai Foo có giống nhau hay không.

Đảm bảo tính toán mã băm tốt (tránh nhiều đối tượng Foo bằng nhau có cùng mã băm), nhưng hãy đảm bảo hai Foo bằng nhau có cùng mã băm. Bạn có thể muốn bắt đầu với Equals-Method và sau đó (trong GetHashCode ()) hoặc mã băm của mọi thành viên mà bạn so sánh trong 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
Bên cạnh đó - nhưng xor (^) tạo ra một tổ hợp kém cho mã băm, vì nó thường dẫn đến nhiều va chạm chéo (tức là {"foo", "bar"} so với {"bar", "foo"}. Tốt hơn lựa chọn là nhân và cộng từng số hạng - tức là 17 * a.GetHashCode () + B.GetHashCode ();
Marc Gravell

2
Marc, tôi hiểu ý bạn. Nhưng làm thế nào để bạn đạt được con số 17 kỳ diệu? Có thuận lợi khi sử dụng một số nguyên tố làm phép nhân để kết hợp các hàm băm không? Nếu vậy, tại sao?
froh42

Tôi có thể đề nghị trả lại: (A + B) .GetHashCode () chứ không phải: 17 * A.GetHashCode () + B.GetHashCode () Điều này sẽ: 1) Ít có khả năng xảy ra va chạm và 2) đảm bảo rằng không có tràn số nguyên.
Charles Burns

(A + B) .GetHashCode () tạo ra một thuật toán băm rất tệ, vì các bộ khác nhau của (A, B) có thể dẫn đến cùng một hàm băm nếu chúng được nối với cùng một chuỗi; "hellow" + "ned" giống với "hell" + "own" và sẽ dẫn đến cùng một hàm băm.
kaesve

@kaesve thì sao về (A + "" + B) .GetHashCode ()?
Vượt thời gian

1

Còn về Hashtablelớp học!

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

Theo cách trên, bạn có thể sử dụng bất kỳ đối tượng nào (đối tượng lớp của bạn) làm khóa Từ điển chung :)


1

Tôi đã từng gặp vấn đề tương tự. Bây giờ tôi có thể sử dụng bất kỳ đối tượng nào tôi đã thử làm khóa do ghi đè Equals và GetHashCode.

Đây là một lớp mà tôi đã xây dựng bằng các phương thức để sử dụng bên trong ghi đè của Equals (object obj) và GetHashCode (). Tôi đã quyết định sử dụng generic và thuật toán băm có thể bao phủ hầu hết các đối tượng. Vui lòng cho tôi biết nếu bạn thấy bất kỳ điều gì ở đây không hoạt động với một số loại đối tượng và bạn có cách cải thiện nó.

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

Đây là cách nó được sử dụng trong một lớp:

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

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.