2009-03-11 42 views
101

Nesneleri Dictionary için anahtar olarak kullanmak istersem, bunları belirli bir şekilde karşılaştırmak için hangi yöntemleri geçersiz kılmam gerekir? Genel sözlük anahtarı olarak bir nesneyi kullanma

Ben özelliklere sahip bir bir sınıf olduğunu varsayalım:

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

    // elided 
} 

Ve ben bir oluşturmak istiyorum:

Dictionary<Foo, List<Stuff>> 

Aynı grubun dikkate alınması gereken aynı FooID ile Foo nesneleri istiyorum. Foo sınıfında hangi yöntemleri geçersiz kılmam gerekiyor?

Özetlemek gerekirse: Foo nesnelerine göre gruplandırılmış Stuff nesnesini listelere ayırmak istiyorum. Stuff nesneleri, kategorilerine bağlamak için FooID olacaktır. Foo için iki Foo'nun aynı olup olmadığını karşılaştırmak için Object.GetHashCode() ve Object.Equals()

Sözlük her değer için bir karma kova hesaplamak için GetHashCode() çağırır ve Eşittir edecek geçersiz kılmak gerekir

cevap

128

, iki önemli yöntemler GetHashCode() ve Equals() bulunmaktadır. İki şeyin eşit olması durumunda (Equals() doğru döndürür), aynı karma kodun olması önemlidir. Örneğin, "FooID;" GetHashCode() olarak, bunu maç olarak isterseniz. Ayrıca IEquatable<Foo> uygulayabilirsiniz, ama bu isteğe bağlıdır:

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

Son olarak, başka bir alternatif aynı şeyi bir IEqualityComparer<T> sağlamaktır. Eğer anahtar olarak Foo nesneyi kullanmak istiyorsunuz Eğer,

Dictionary<int, List<Stuff>> 

: Eğer FooID grubu için tanımlayıcı olmak istiyorum gibi

+4

+1 ve ben bu iş parçacığı kaçırmak istemiyorum ama GetHashCode() FooId.GetHashCode() döndürmelidir izlenim altında idi. Bu doğru kalıp değil mi? –

+7

@Ken -, sadece gerekli özellikleri sağlayan bir int geri dönmesi gerekiyor. FooID, FooID.GetHashCode() öğesinin yanı sıra hangi FooID'yi de yapacak. Uygulama ayrıntısı olarak, Int32.GetHashCode(), "bunu döndür"; Diğer türler (dize vb.) Için evet: .GetHashCode() çok yararlı olacaktır. –

+2

Teşekkürler! Ben IEqualityComparer ile gittim, çünkü sadece yöntemleri kullanmam gereken Dikaryoner için oldu. – Dana

8

.

İyi karma kodları hesapladığınızdan emin olun (aynı karma kodu olan birçok eşit Foo nesnesinden kaçının), ancak iki eşittir Foos'un aynı karma kodu olduğundan emin olun. Equals-Method ile başlayıp (GetHashCode()) xor ile Equals'da karşılaştırdığınız her üyenin hash kodunu başlatmak isteyebilirsiniz. Varsayılan olarak

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

- ama genellikle diyagonal çarpışmaların bir sürü yol açar olarak xor (^), karma-kodları için kötü bir bağdaştırıcının yapar (yani {"foo", "bar"} vs {"bar", "foo"}. Daha iyi bir seçim, her terimi çoğaltır ve eklenir - yani 17 * a.GetHashCode() + B.GetHashCode(); –

+2

Marc, I Ne demek istediğini anladın mı? Ama 17 numaralı büyüye nasıl ulaşıyorsun? Karmaları birleştirmek için bir çoğaltıcı olarak bir asal sayı kullanmak avantajlı mı? Eğer öyleyse, neden? – froh42

+0

Geri dönüşü önerebilir miyim: (A + B) .GetHashCode() yerine: 17 * A.GetHashCode() + B.GetHashCode() Bu: 1) Bir çarpışma olasılığının daha düşük olması ve 2) tam sayı taşması olmadığından emin olun. –

29

, bunun yerine Foo nesnesinin sözlükte anahtar olarak bu kullanmalıdır Sadece FooID özelliğini dikkate almak için GetHashCode ve Equals yöntemini uygular. Name özelliği yalnızca Dictionary söz konusu olduğunda ölü ağırlık olur, bu nedenle int için Foo sarıcı olarak kullanabilirsiniz.

Bu nedenle, FooID değerini doğrudan kullanmak daha iyidir ve Dictionary zaten bir anahtar olarak bir int kullanarak desteklediğinden, hiçbir şey uygulamak zorunda kalmazsınız.

Düzenleme:
zaten anahtar olarak Foo sınıfını kullanmak istiyorsanız, IEqualityComparer<Foo> uygulanması kolaydır:

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

Kullanımı:

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

Daha doğru bir şekilde int, anahtar olarak kullanılmak üzere gereken yöntemleri/arabirimleri zaten desteklemektedir. Sözlük, int veya herhangi bir başka türle ilgili doğrudan bilgiye sahip değildir. –

+0

Bunu düşündüm, ancak çeşitli nedenlerden ötürü, nesnelerini sözlük tuşları olarak kullanmak daha temiz ve daha kullanışlıydı. – Dana

+1

Sadece, yalnızca anahtarı anahtar olarak kullandığınız için, yalnızca nesneyi anahtar olarak kullandığınız anlaşılıyor. – Guffa

1

hangi sınıf Hashtable hakkında!

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

Yukarıdaki şekilde, genel bir sözlük anahtarla :)

0

Ben aynı problem vardı gibi herhangi bir nesne (sınıf nesnesi) kullanabilir. Artık, Equals ve GetHashCode'u geçersiz kılan bir anahtar olarak denediğim herhangi bir nesneyi kullanabilirim.

Eşitliklerin (object obj) ve GetHashCode() geçersiz kılmalarının içinde kullanılacak yöntemlerle oluşturduğum bir sınıf. Generikleri ve çoğu nesneyi kapsayabilmesi gereken bir karma algoritması kullanmaya karar verdim. Burada, bazı nesne türleri için işe yaramayan bir şey görüyorsanız lütfen bize bildirin ve bunu iyileştirmenin bir yolu var. İşte

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

bir sınıfında kullanılan nasıl: Kenara

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

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      return new Equality<ClassName>().GetHashCode(this); 
     } 
    } 
İlgili konular