Bir nesnenin alanını genel bir Sözlük anahtarı olarak kullanma


120

Nesneleri a için anahtar olarak kullanmak istersem, Dictionaryonları belirli bir şekilde karşılaştırmak için hangi yöntemleri geçersiz kılmam gerekir?

Diyelim ki şu özelliklere sahip bir sınıfım var:

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

    // elided
} 

Ve bir oluşturmak istiyorum:

Dictionary<Foo, List<Stuff>>

FooAynı FooIDolan nesnelerin aynı grup olarak kabul edilmesini istiyorum . FooSınıfta hangi yöntemleri geçersiz kılmam gerekecek ?

Özetlemek gerekirse: StuffNesneleri nesnelere göre gruplanmış listeler halinde kategorilere ayırmak istiyorum Foo. Stuffnesneler, FooIDonları kategorilerine bağlayacaktır.

Yanıtlar:


151

Varsayılan olarak, iki önemli yöntem GetHashCode()ve Equals(). İki şey eşitse ( Equals()doğru döndürürse), aynı karma koda sahip olmaları önemlidir . Örneğin, "FooID'yi döndürebilirsiniz;" olarak GetHashCode()siz eşleşme olarak bu istiyorum. Ayrıca uygulayabilirsiniz IEquatable<Foo>, ancak 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 IEqualityComparer<T>de aynı şeyi yapmaktır.


5
+1 ve bu iş parçacığını ele geçirmek istemiyorum ama GetHashCode () FooId.GetHashCode () döndürmesi gerektiği izlenimindeydim. Bu doğru model değil mi?
Ken Browning

8
@Ken - peki, sadece gerekli özellikleri sağlayan bir int döndürmesi gerekiyor. Hangi FooID, FooID.GetHashCode () kadar iyi çalışır. Bir uygulama ayrıntısı olarak, Int32.GetHashCode () "bunu döndür;" dür. Diğer türler için (dize vb.), O zaman yes: .GetHashCode () çok yararlı olacaktır.
Marc Gravell

2
Teşekkürler! IEqualityComparer <T> ile gittim, çünkü sadece Dicarionary için geçersiz kılma yöntemlerine ihtiyacım vardı.
Dana

1
Hashtable'lara (Dictionary <T>, Dictionary, HashTable, vb.) Dayalı konteynerlerin performansının, kullanılan hash fonksiyonunun kalitesine bağlı olduğunu bilmelisiniz. Hash kodu olarak sadece FooID kullanırsanız, kapsayıcılar çok kötü çalışabilir.
Jørgen Fogh

2
@ JørgenFogh Bunun çok farkındayım; sunulan örnek, belirtilen amaç ile tutarlıdır. Karma değişmezlikle ilgili birçok ilgili endişe vardır; Kimlikler, adlardan daha az değişir ve genellikle güvenilir bir şekilde benzersizdir ve eşdeğerlik göstergeleridir. Yine de önemsiz olmayan bir konu.
Marc Gravell

33

FooIDGrubun tanımlayıcısı olmasını istediğinizden , bunu Foo nesnesi yerine sözlükte anahtar olarak kullanmalısınız:

Dictionary<int, List<Stuff>>

FooNesneyi anahtar olarak kullanırsanız, yalnızca özelliği dikkate almak için GetHashCodeve Equalsyöntemini uygularsınız FooID. NameKadarıyla özellik sadece ölü ağırlık olurdu Dictionaryendişeliydi, bu yüzden sadece kullanacağınız Foobir için sarıcı olarak int.

Bu nedenle, FooIDdeğeri doğrudan kullanmak daha iyidir ve o zaman Dictionaryzaten intbir anahtar olarak kullanmayı desteklediği için hiçbir şey uygulamanıza gerek kalmaz .

Düzenleme: Sınıfı yine de anahtar olarak
kullanmak istiyorsanız Foo, 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ğrusu, int bir anahtar olarak kullanılması için gereken yöntemleri / arabirimleri zaten desteklemektedir. Sözlük, int veya diğer türler hakkında doğrudan bilgi sahibi değildir.
Jim Mischel

Bunu düşündüm, ancak çeşitli nedenlerden dolayı nesneleri sözlük anahtarı olarak kullanmak daha temiz ve daha uygun oldu.
Dana

1
Görünüşe göre sadece nesneyi anahtar olarak kullanıyorsunuz, çünkü gerçekten sadece id'yi anahtar olarak kullanıyorsunuz.
Guffa

8

Foo için object.GetHashCode () ve object.Equals () 'i geçersiz kılmanız gerekir.

Sözlük, her değer için bir karma grubu hesaplamak için GetHashCode () 'u çağırır ve iki Foo'nun aynı olup olmadığını karşılaştırmak için Eşittir.

İyi hash kodlarını hesapladığınızdan emin olun (aynı hashcode'a sahip birçok eşit Foo nesnesinden kaçının), ancak iki eşit Foo'nun aynı hash koduna sahip olduğundan emin olun. Eşittir Yöntemi ve ardından (GetHashCode ()) x veya Eşittir'de karşılaştırdığınız her üyenin karma kodu ile başlamak isteyebilirsiniz.

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
Bir kenara - ama xor (^), genellikle çok sayıda çapraz çarpışmaya yol açtığı için karma kodlar için zayıf bir birleştirici yapar (yani {"foo", "bar"} vs {"bar", "foo"}. Daha iyi seçim her terimi çarpmak ve toplamaktır - yani 17 * a.GetHashCode () + B.GetHashCode ();
Marc Gravell

2
Marc, ne demek istediğini anlıyorum. Ama sihirli sayı 17'ye nasıl ulaşırsınız? Karmaları birleştirmek için bir çarpan olarak bir asal sayı kullanmak avantajlı mıdır? Öyleyse neden?
froh42

Geri dönmeyi önerebilir miyim: (A + B) .GetHashCode () yerine: 17 * A. GetHashCode () + B. GetHashCode () Bu: 1) Çarpışma olasılığı daha düşük olacak ve 2) tamsayı taşması.
Charles Burns

(A + B) .GetHashCode () çok kötü bir karma algoritma oluşturur, çünkü farklı (A, B) kümeleri aynı dizeye birleştirilirse aynı hash ile sonuçlanabilir; "merhaba" + "ned", "cehennem" + "sahip olunan" ile aynıdır ve aynı hash ile sonuçlanır.
kaesve

@kaesve nasıl (A + "" + B) .GetHashCode ()?
Zamansız

1

Peki ya Hashtablesınıf!

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, herhangi bir nesneyi (sınıf nesneniz) genel Sözlük anahtarı olarak kullanabilirsiniz :)


1

Ben de aynı sorunu yaşadım. Equals ve GetHashCode'u geçersiz kıldığı için artık denediğim herhangi bir nesneyi anahtar olarak kullanabilirim.

Eşittir (nesne obj) ve GetHashCode () geçersiz kılmalarının içinde kullanmak için yöntemlerle oluşturduğum bir sınıf. Nesnelerin çoğunu kapsayabilecek jenerikler ve bir karma algoritma kullanmaya karar verdim. Burada bazı nesne türleri için işe yaramayan bir şey görürseniz ve onu iyileştirmenin bir yolunu bulursanız lütfen bana bildirin.

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

Sınıfta şu şekilde kullanılır:

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

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.