.NET benzersiz nesne tanımlayıcısı


118

Bir örneğin benzersiz bir tanımlayıcısını almanın bir yolu var mı?

GetHashCode()aynı örneğe işaret eden iki referans için aynıdır. Bununla birlikte, iki farklı örnek (oldukça kolay bir şekilde) aynı karma kodunu alabilir:

Hashtable hashCodesSeen = new Hashtable();
LinkedList<object> l = new LinkedList<object>();
int n = 0;
while (true)
{
    object o = new object();
    // Remember objects so that they don't get collected.
    // This does not make any difference though :(
    l.AddFirst(o);
    int hashCode = o.GetHashCode();
    n++;
    if (hashCodesSeen.ContainsKey(hashCode))
    {
        // Same hashCode seen twice for DIFFERENT objects (n is as low as 5322).
        Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")");
        break;
    }
    hashCodesSeen.Add(hashCode, null);
}

Bir hata ayıklama eklentisi yazıyorum ve programın çalışması sırasında benzersiz olan bir referans için bir tür kimlik almam gerekiyor.

Çöp toplayıcı (GC) öbeği sıkıştırana kadar (= nesneleri taşır = adresleri değiştirene) benzersiz olan örneğin dahili ADDRESS'ini almayı zaten başardım.

Yığın Taşma sorusu Object.GetHashCode () için varsayılan uygulama ilgili olabilir.

Hata ayıklayıcı API'sini kullanarak ayıklanan bir programdaki nesnelere eriştiğim için nesneler benim kontrolüm altında değil. Nesnelerin kontrolü bende olsaydı, kendi benzersiz tanımlayıcılarımı eklemek önemsiz olurdu.

Bir hashtable ID -> nesnesi oluşturmak için benzersiz bir kimlik, daha önce görülen nesneleri arayabilmek için istedim. Şimdilik bunu şu şekilde çözdüm:

Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode')
Find if object seen(o) {
    candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode.
    If no candidates, the object is new
    If some candidates, compare their addresses to o.Address
        If no address is equal (the hash code was just a coincidence) -> o is new
        If some address equal, o already seen
}

Yanıtlar:


42

Referans , nesnenin benzersiz tanımlayıcısıdır. Bunu dizge vb. Gibi bir şeye dönüştürmenin herhangi bir yolunu bilmiyorum. Sıkıştırma sırasında referansın değeri değişecek (gördüğünüz gibi), ancak önceki her A değeri B değerine değiştirilecek. güvenli kod söz konusu olduğunda hala benzersiz bir kimliktir.

İlgili nesneler sizin kontrolünüz altındaysa , seçtiğiniz bir kimliğe (GUID, tamsayı, ne olursa olsun) bir referanstan zayıf referanslar kullanarak (çöp toplamayı önlemek için) bir eşleme oluşturabilirsiniz . Ancak bu, belirli bir miktar ek yük ve karmaşıklık ekleyecektir.


1
Sanırım aramalar için izlediğiniz tüm referansları yinelemeniz gerekecek: Aynı nesneye yapılan WeakReference birbirine eşit değildir, bu yüzden gerçekten başka bir şey yapamazsınız.
Roman Starkov

1
Her bir nesneye benzersiz bir 64 bitlik kimlik atanmasının, özellikle bu tür kimlikler sıralı olarak verilmişse, bazı faydaları olabilir. Yararlılığın maliyeti haklı çıkaracağından emin değilim, ancak böyle bir şey, iki farklı değişmez nesneyi karşılaştırır ve onları eşit bulursa yardımcı olabilir; mümkün olduğunda yenisine yapılan referansı eskisine atıfta bulunularak üzerine yazılırsa, özdeş, ancak farklı nesnelere birçok fazladan referanstan kaçınılabilir.
supercat

1
“Tanıtıcı.” Bu kelimenin düşündüğün anlamını ifade ettiğini sanmıyorum.
Slipp D. Thompson

5
@ SlippD.Thompson: Hayır, hala 1'e 1 ilişki. Herhangi bir nesneye atıfta bulunan yalnızca tek bir referans değeri vardır. Bu değer bellekte birçok kez görünebilir (örneğin, birden çok değişkenin değeri olarak), ancak yine de tek bir değerdir. Bu bir ev adresi gibi: Ev adresimi birçok kağıda birden fazla yazabilirim, ancak bu yine de evimin tanımlayıcısıdır. Herhangi iki özdeş olmayan referans değerleri gerekmektedir farklı nesneler bakınız - en azından C #.
Jon Skeet

1
@supercat: "Kapsüllenmiş kimlikler" anlayışımızda farklı olabileceğimizi düşünüyorum - ama sanırım muhtemelen hiç kimsenin halihazırda sahip olduğumuzdan daha ileri gitmesine de yardım etmiyoruz :) Eğer uzun uzun tartışmamız gereken konulardan sadece biri
Jon Skeet

72

Yalnızca .NET 4 ve sonrası

Herkese iyi haberler!

Bu iş için mükemmel araç .NET 4'te yerleşiktir ve adı verilir ConditionalWeakTable<TKey, TValue>. Bu sınıf:

  • (Bu her ne kadar çok bir sözlük gibi yönetilen nesne örnekleri rastgele verileri ilişkilendirmek için kullanılabilecek olan bir sözlük değil)
  • bellek adreslerine bağlı değildir, dolayısıyla yığını sıkıştıran GC'ye karşı bağışıktır
  • nesneleri sırf tabloya anahtar olarak girildikleri için canlı tutmaz, bu nedenle sürecinizdeki her nesneyi sonsuza kadar yaşatmadan kullanılabilir
  • nesne kimliğini belirlemek için referans eşitliğini kullanır; geçiş, sınıf yazarları bu davranışı değiştiremez, böylece herhangi bir türdeki nesnede tutarlı bir şekilde kullanılabilir
  • anında doldurulabilir, bu nedenle nesne yapıcılarının içine kod eklemenizi gerektirmez

5
Sadece bütünlük için: kendi iç işleyişine ConditionalWeakTablegüvenir RuntimeHelpers.GetHashCodeve object.ReferenceEqualsonu yapar. Davranış, IEqualityComparer<T>bu iki yöntemi kullanan bir yapı oluşturmakla aynıdır . Performansa ihtiyacınız varsa, aslında bunu yapmanızı öneririm, çünkü ConditionalWeakTableiş parçacığını güvenli hale getirmek için tüm işlemlerinin etrafında bir kilit vardır.
atlaste

1
@StefandeBruijn: A , yalnızca karşılık gelen başka bir yerde tutulan referans kadar güçlü olan ConditionalWeakTableher birine bir referansta bulunur . Evrenin herhangi bir yerinde var olan tek referansı tutan bir nesne , anahtar olduğunda otomatik olarak varlığını durduracaktır. ValueKeyConditionalWeakTable
supercat

41

Ayrıldı ObjectIDGenerator sınıfına? Bu, yapmaya çalıştığınız şeyi ve Marc Gravell'in tarif ettiği şeyi yapar.

ObjectIDGenerator önceden tanımlanan nesnelerin kaydını tutar. Bir nesnenin kimliğini sorduğunuzda, ObjectIDGenerator mevcut ID'yi döndürüp döndürmeyeceğini veya yeni bir ID oluşturup hatırlayacağını bilir.

Kimlikler, ObjectIDGenerator örneğinin ömrü boyunca benzersizdir. Genel olarak, bir ObjectIDGenerator ömrü onu yaratan Biçimlendirici kadar sürer. Nesne kimlikleri yalnızca belirli bir serileştirilmiş akış içinde anlam taşır ve serileştirilmiş nesne grafiği içinde hangi nesnelerin diğerlerine referansları olduğunu izlemek için kullanılır.

Bir hash tablosu kullanarak, ObjectIDGenerator hangi ID'nin hangi nesneye atandığını korur. Her bir nesneyi benzersiz şekilde tanımlayan nesne başvuruları, çalışma zamanı çöp toplama yığınındaki adreslerdir. Nesne referans değerleri serileştirme sırasında değişebilir, ancak bilgilerin doğru olması için tablo otomatik olarak güncellenir.

Nesne kimlikleri 64 bitlik sayılardır. Tahsis birden başlar, bu nedenle sıfır hiçbir zaman geçerli bir nesne kimliği değildir. Biçimlendirici, değeri boş bir başvuru (Visual Basic'te Nothing) olan bir nesne başvurusunu temsil etmek için sıfır değeri seçebilir.


5
Reflector bana ObjectIDGenerator'ün varsayılan GetHashCode uygulamasına dayanan bir hashtable olduğunu (yani kullanıcı aşırı yüklemelerini kullanmadığını) söylüyor.
Anton Tykhyy

Yazdırılabilir benzersiz kimlikler gerektiğinde muhtemelen en iyi çözüm.
Roman Starkov

ObjectIDGenerator telefonda da uygulanmıyor.
Anthony Wieser

ObjectIDGenerator'ın tam olarak ne yaptığını anlamıyorum, ancak RuntimeHelpers.GetHashCode kullanırken bile çalışıyor gibi görünüyor. İkisini de test ettim ve yalnızca RuntimeHelpers.GetHashCode benim durumumda başarısız oluyor.
Daniel Bişar

+1 - Oldukça düzgün çalışır (en azından masaüstünde).
Hot Licks

37

RuntimeHelpers.GetHashCode()yardımcı olabilir ( MSDN ).


2
Bu yardımcı olabilir, ancak bir maliyetle - IIRC, temel nesneyi kullanarak.GetHashCode (), ücretsiz olmayan bir senkronizasyon bloğu tahsis etmelidir. Yine de güzel bir fikir - benden +1.
Jon Skeet

Teşekkürler, bu yöntemi bilmiyordum. Ancak, benzersiz bir karma kod üretmez (sorudaki örnek kodla tam olarak aynı davranır). Kullanıcı hash kodunu geçersiz kılarsa, varsayılan sürümü çağırmak faydalı olacaktır.
Martin Konicek

1
Çok fazlasına ihtiyacınız yoksa GCHandle'ı kullanabilirsiniz (aşağıya bakın).
Anton Tykhyy

42
Çok saygın bir yazarın .NET üzerine yazdığı bir kitap, RuntimeHelpers.GetHashCode () 'un bir AppDomain içinde benzersiz bir kod üreteceğini ve Microsoft'un GetUniqueObjectID adını vermiş olabileceğini belirtir. Bu tamamen yanlış. Test sırasında, genellikle bir nesnenin 10.000 örneğini (WinForms TextBox) oluşturduğumda bir kopya elde edeceğimi ve asla 30.000'i geçemeyeceğimi fark ettim. Sözde benzersizliğe dayanan kod, 1 / 10'dan fazla nesne oluşturmadan bir üretim sisteminde aralıklı çökmelere neden oluyordu.
Jan Hettich

3
@supercat: Aha - 2003'ten .NET 1.0 ve 1.1'den bazı kanıtlar bulduk. Görünüşe göre .NET 2 için değiştirmeyi planlıyorlardı: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet

7

Bir saniyede kendi şeyinizi geliştirebilirsiniz. Örneğin:

   class Program
    {
        static void Main(string[] args)
        {
            var a = new object();
            var b = new object();
            Console.WriteLine("", a.GetId(), b.GetId());
        }
    }

    public static class MyExtensions
    {
        //this dictionary should use weak key references
        static Dictionary<object, int> d = new Dictionary<object,int>();
        static int gid = 0;

        public static int GetId(this object o)
        {
            if (d.ContainsKey(o)) return d[o];
            return d[o] = gid++;
        }
    }   

Benzersiz bir kimlik olarak neye sahip olmak istediğinizi seçebilirsiniz, örneğin System.Guid.NewGuid () veya en hızlı erişim için basitçe tamsayı.


2
Bunun için ihtiyacınız olan şey Disposeböceklerse yardımcı olmayacaktır , çünkü bu her türlü imhayı önleyecektir.
Roman Starkov

1
Bu oldukça Object.Equals için aynı değerleri döndürür nesneleri çöken yerine kimlik sözlük kullanımları eşitlik olarak çalışmıyor
Anthony Wieser

1
Bu, nesneyi canlı tutacaktır.
Martin Lottering

1
@MartinLottering ya ConditionalWeakTable <object, idType> kullanıyorsa?
Demetris Leptos

7

Bu yönteme ne dersiniz:

İlk nesnedeki bir alanı yeni bir değere ayarlayın. İkinci nesnedeki aynı alan aynı değere sahipse, muhtemelen aynı örnektir. Aksi takdirde, farklı olarak çıkın.

Şimdi ilk nesnedeki alanı farklı bir yeni değere ayarlayın. İkinci nesnedeki aynı alan farklı bir değere değiştiyse, kesinlikle aynı örnektir.

İlk nesnedeki alanı çıkışta orijinal değerine geri ayarlamayı unutmayın.

Sorunlar?


4

Visual Studio'da benzersiz bir nesne tanımlayıcısı yapmak mümkündür: İzleme penceresinde, nesne değişkenine sağ tıklayın ve bağlam menüsünden Nesne Kimliği Yap'ı seçin.

Ne yazık ki, bu manuel bir adım ve tanımlayıcıya kod aracılığıyla erişilebileceğini sanmıyorum.


Visual Studio'nun hangi sürümlerinde bu özellik var? Örneğin, Express sürümleri?
Peter Mortensen

3

Böyle bir tanımlayıcıyı kendiniz manuel olarak, örneğin içinde veya dışında atamanız gerekir.

Bir veritabanı ile ilgili kayıtlar için, birincil anahtar yararlı olabilir (ancak yine de kopyalar alabilirsiniz). Alternatif olarak, ya a kullanın ya Guidda kendi sayacınızı kullanarak tahsis edin Interlocked.Increment(ve taşma olasılığı olmayacak kadar büyük yapın).



1

Burada verdiğim bilgiler yeni değil, bunu tamlık için ekledim.

Bu kodun fikri oldukça basit:

  • Nesnelerin, varsayılan olarak orada olmayan benzersiz bir kimliğe ihtiyacı vardır. Bunun yerine, bir sonraki en iyi şeye güvenmeliyiz, bu da RuntimeHelpers.GetHashCodebize bir tür benzersiz kimlik
  • Benzersizliği kontrol etmek için bu, kullanmamız gerektiği anlamına gelir object.ReferenceEquals
  • Bununla birlikte, yine de benzersiz bir kimliğimiz olmasını istiyoruz, bu yüzden GUIDtanım gereği benzersiz olan bir ekledim .
  • Çünkü mecbur kalmazsam her şeyi kilitlemeyi sevmiyorum, kullanmıyorum ConditionalWeakTable.

Bu size aşağıdaki kodu verecektir:

public class UniqueIdMapper
{
    private class ObjectEqualityComparer : IEqualityComparer<object>
    {
        public bool Equals(object x, object y)
        {
            return object.ReferenceEquals(x, y);
        }

        public int GetHashCode(object obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }

    private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
    public Guid GetUniqueId(object o)
    {
        Guid id;
        if (!dict.TryGetValue(o, out id))
        {
            id = Guid.NewGuid();
            dict.Add(o, id);
        }
        return id;
    }
}

Bunu kullanmak UniqueIdMapperiçin, nesnelerin bir örneğini oluşturun ve nesneler için döndürdüğü GUID'leri kullanın.


ek

Yani burada biraz daha fazlası var; hakkında biraz yazmama izin verin ConditionalWeakTable.

ConditionalWeakTablebirkaç şey yapar. En önemli şey çöp toplayıcıyı umursamaması, yani bu tabloda referans verdiğiniz nesneler ne olursa olsun toplanacak. Bir nesneye bakarsanız, temelde yukarıdaki sözlükle aynı şekilde çalışır.

Meraklı hayır mı? Sonuçta, bir nesne GC tarafından toplanırken, nesneye referans olup olmadığını kontrol eder ve varsa onları toplar. Öyleyse, öğesinden bir nesne varsa ConditionalWeakTable, o zaman neden başvurulan nesne toplanacak?

ConditionalWeakTablediğer bazı .NET yapılarının da kullandığı küçük bir numara kullanır: nesneye bir başvuru depolamak yerine, aslında bir IntPtr depolar. Bu gerçek bir referans olmadığı için nesne toplanabilir.

Yani, bu noktada ele alınması gereken 2 sorun var. İlk olarak, nesneler yığın üzerinde hareket ettirilebilir, öyleyse IntPtr olarak ne kullanacağız? İkincisi, nesnelerin aktif bir referansı olduğunu nasıl bilebiliriz?

  • Nesne öbek üzerine sabitlenebilir ve gerçek işaretçisi saklanabilir. GC, kaldırılmak üzere nesneye çarptığında, onu çözer ve toplar. Ancak bu, sabitlenmiş bir kaynak elde ettiğimiz anlamına gelir; bu, çok fazla nesneniz varsa (bellek parçalanma sorunları nedeniyle) iyi bir fikir değildir. Muhtemelen böyle çalışmıyor.
  • GC bir nesneyi taşıdığında, geri çağırır ve bu daha sonra referansları güncelleyebilir. Dışarıdan gelen aramalar tarafından değerlendirilerek bu şekilde uygulanıyor olabilir DependentHandle- ancak biraz daha karmaşık olduğuna inanıyorum.
  • Nesnenin kendisine işaretçi değil, ancak GC'deki tüm nesnelerin listesinde bir işaretçi saklanır. IntPtr, bu listedeki bir dizin veya bir göstericidir. Liste yalnızca bir nesne nesilleri değiştirdiğinde değişir, bu noktada basit bir geri arama işaretçileri güncelleyebilir. Mark & ​​Sweep'in nasıl çalıştığını hatırlarsanız, bu daha mantıklıdır. Sabitleme yok ve sökme işlemi eskisi gibi. Bunun nasıl çalıştığına inanıyorum DependentHandle.

Bu son çözüm, çalışma zamanının liste paketlerini açıkça serbest bırakılıncaya kadar yeniden kullanmamasını ve ayrıca tüm nesnelerin çalışma zamanına yapılan bir çağrı ile alınmasını gerektirir.

Bu çözümü kullandıklarını varsayarsak, ikinci sorunu da ele alabiliriz. Mark & ​​Sweep algoritması hangi nesnelerin toplandığını takip eder; toplanır toplanmaz, bu noktada biliyoruz. Nesne, nesnenin orada olup olmadığını kontrol ettikten sonra, işaretçiyi ve liste girişini kaldıran 'Ücretsiz'i çağırır. Nesne gerçekten gitti.

Bu noktada dikkat edilmesi gereken önemli bir nokta, ConditionalWeakTablebirden çok iş parçacığında güncellenirse ve iş parçacığı güvenli değilse , işlerin korkunç şekilde yanlış gitmesidir . Sonuç, bir bellek sızıntısı olur. Bu nedenle tüm aramalar, ConditionalWeakTablebunun olmamasını sağlayan basit bir 'kilit' yapar.

Dikkat edilmesi gereken bir diğer husus da, girişleri temizlemenin arada bir yapılması gerektiğidir. Gerçek nesneler GC tarafından temizlenecek, ancak girişler temizlenmemektedir. Bu yüzden ConditionalWeakTablesadece boyut olarak büyüyor. Belli bir limite ulaştığında (karmadaki çarpışma şansı ile belirlenir), Resizenesnelerin temizlenmesinin gerekip gerekmediğini kontrol eden bir a tetikler - eğer varsa free, IntPtrtutamacı kaldırarak GC sürecinde çağrılır .

Bunun da neden DependentHandledoğrudan açığa çıkmadığına inanıyorum - bir şeylerle uğraşmak ve sonuç olarak bir bellek sızıntısı yaşamak istemezsiniz. Bunun için bir sonraki en iyi şey a'dır (bir nesne yerine WeakReferencebir de depolar IntPtr) - ancak maalesef 'bağımlılık' özelliğini içermiyor.

Geriye kalan şey, mekanikle oynamanız, böylece bağımlılığı eylemde görebilmenizdir. Birden çok kez başladığınızdan ve sonuçları izlediğinizden emin olun:

class DependentObject
{
    public class MyKey : IDisposable
    {
        public MyKey(bool iskey)
        {
            this.iskey = iskey;
        }

        private bool disposed = false;
        private bool iskey;

        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                Console.WriteLine("Cleanup {0}", iskey);
            }
        }

        ~MyKey()
        {
            Dispose();
        }
    }

    static void Main(string[] args)
    {
        var dep = new MyKey(true); // also try passing this to cwt.Add

        ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
        cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.

        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();

        Console.WriteLine("Wait");
        Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
    }

1
A ConditionalWeakTabledaha iyi olabilir, çünkü nesnelerin temsillerini yalnızca onlara referanslar varken devam ettirir. Ayrıca, Int64nesnelere kalıcı bir sıralama verilebileceği için bir GUID'den daha iyi olabileceğini öneririm . Bu tür şeyler senaryoları kilitleme yararlı olabilir (örneğin bir çoklu kilitlerin elde gerekecektir biri tüm kod bazı tanımlanmış sırayla kadar yoksa kilitlenme önlemek olabilir, ama bunun için işe orada olmalıdır olmak tanımlanmış bir sipariş).
supercat

@supercat longS hakkında emin olun; senaryonuza bağlıdır - f.ex. dağıtılmış sistemler bazen GUIDs ile çalışmak daha kullanışlıdır . Gelince ConditionalWeakTable: haklısın; DependentHandlecanlılığı kontrol eder (NOT: yalnızca şey yeniden boyutlandırıldığında!), bu burada yararlı olabilir. Yine de, performansa ihtiyacınız varsa, kilitleme orada bir sorun haline gelebilir, bu nedenle bu durumda bunu kullanmak ilginç olabilir ... dürüst olmak gerekirse, uygulamasından kişisel olarak hoşlanmıyorum ConditionalWeakTable, bu da muhtemelen benim basit Dictionary- hatta haklı olsan da.
atlaste

Gerçekte nasıl ConditionalWeakTableçalıştığını uzun zamandır merak ediyorum . Yalnızca öğelerin eklenmesine izin vermesi, eşzamanlılıkla ilgili ek yükü en aza indirecek şekilde tasarlandığını düşündürüyor, ancak dahili olarak nasıl çalıştığı hakkında hiçbir fikrim yok. DependentHandleTablo kullanmayan basit bir paketleyicinin olmadığını merak ediyorum , çünkü bir nesnenin diğerinin ömrü boyunca hayatta kalmasını sağlamanın önemli olduğu zamanlar vardır, ancak ikinci nesnede referans için yer yoktur. ilkine.
supercat

@supercat Nasıl çalıştığını düşündüğümle ilgili bir ek yayınlayacağım.
atlaste

ConditionalWeakTableTabloda saklanan izin vermez girişleri değiştirilecek. Bu nedenle, bellek engelleri kullanılarak güvenli ancak kilitler kullanılarak uygulanabileceğini düşünürdüm. Tek sorunlu durum, iki iş parçacığının aynı anahtarı aynı anda eklemeye çalışması olabilir; Bu sorun, "ekle" yönteminin bir öğe eklendikten sonra bir bellek engeli gerçekleştirmesi ve ardından tam olarak bir öğenin bu anahtara sahip olduğundan emin olmak için taramasıyla çözülebilir. Birden fazla öğe aynı anahtara sahipse, bunlardan biri "ilk" olarak tanımlanabilir, böylece diğerlerini elemek mümkün olacaktır.
supercat

0

Belirli bir kullanım için kendi kodunda bir modül yazıyorsanız, majkinetor yöntemi MIGHT çalıştık. Ama bazı sorunlar var.

Birincisi , resmi belge yok DEĞİL garanti etmelerini GetHashCode()döner bir benzersiz tanımlayıcı (bkz Object.GetHashCode Yöntemi () ):

Eşit karma kodların nesne eşitliğini ifade ettiğini varsaymamalısınız.

İkinci olarak , GetHashCode()çoğu durumda işe yarayacak çok az miktarda nesneniz olduğunu varsayalım , bu yöntem bazı türler tarafından geçersiz kılınabilir.
Örneğin, bir C sınıfı kullanıyorsunuz ve bu GetHashCode()her zaman 0 döndürmek için geçersiz kılar . Sonra her C nesnesi aynı hash kodunu alır. Maalesef Dictionary, HashTableve diğer bazı ilişkisel kaplar bu yöntemi kullanmak yapacaktır:

Karma kod, Dictionary <TKey, TValue> sınıfı, Hashtable sınıfı veya DictionaryBase sınıfından türetilmiş bir tür gibi karma tabanlı bir koleksiyondaki bir nesneyi eklemek ve tanımlamak için kullanılan sayısal bir değerdir. GetHashCode yöntemi, nesne eşitliğinin hızlı kontrollerine ihtiyaç duyan algoritmalar için bu karma kodu sağlar.

Dolayısıyla, bu yaklaşımın büyük sınırlamaları var.

Ve dahası , genel amaçlı bir kitaplık oluşturmak isterseniz ne olur? Yalnızca kullanılan sınıfların kaynak kodunu değiştiremezsiniz, aynı zamanda davranışları da tahmin edilemez.

Jon ve Simon'ın yanıtlarını göndermelerine minnettarım ve aşağıda bir kod örneği ve performansla ilgili bir öneri yayınlayacağım.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections.Generic;


namespace ObjectSet
{
    public interface IObjectSet
    {
        /// <summary> check the existence of an object. </summary>
        /// <returns> true if object is exist, false otherwise. </returns>
        bool IsExist(object obj);

        /// <summary> if the object is not in the set, add it in. else do nothing. </summary>
        /// <returns> true if successfully added, false otherwise. </returns>
        bool Add(object obj);
    }

    public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            return objectSet.TryGetValue(obj, out tryGetValue_out0);
        }

        public bool Add(object obj) {
            if (IsExist(obj)) {
                return false;
            } else {
                objectSet.Add(obj, null);
                return true;
            }
        }

        /// <summary> internal representation of the set. (only use the key) </summary>
        private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>();

        /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary>
        private static object tryGetValue_out0 = null;
    }

    [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")]
    public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            bool firstTime;
            idGenerator.HasId(obj, out firstTime);
            return !firstTime;
        }

        public bool Add(object obj) {
            bool firstTime;
            idGenerator.GetId(obj, out firstTime);
            return firstTime;
        }


        /// <summary> internal representation of the set. </summary>
        private ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    }
}

Benim ObjectIDGeneratortestimde, fordöngüde 10.000.000 nesne (yukarıdaki koddan 10x) oluştururken çok fazla nesne olduğundan şikayet etmek için bir istisna atacak .

Ayrıca, kıyaslama sonucu, ConditionalWeakTableuygulamanın, uygulamadan 1,8 kat daha hızlı olmasıdır ObjectIDGenerator.

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.