.NET pengenal objek unik


118

Apakah ada cara untuk mendapatkan pengenal unik dari sebuah instance?

GetHashCode()adalah sama untuk dua referensi yang menunjuk ke instance yang sama. Namun, dua contoh berbeda dapat (dengan mudah) mendapatkan kode hash yang sama:

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

Saya sedang menulis addin debugging, dan saya perlu mendapatkan semacam ID untuk referensi yang unik selama menjalankan program.

Saya sudah berhasil mendapatkan ALAMAT internal dari instance, yang unik sampai pengumpul sampah (GC) memadatkan heap (= memindahkan objek = mengubah alamat).

Pertanyaan Stack Overflow Implementasi default untuk Object.GetHashCode () mungkin terkait.

Objek tidak di bawah kendali saya karena saya mengakses objek dalam program yang sedang di-debug menggunakan API debugger. Jika saya mengontrol objek, menambahkan pengenal unik saya sendiri akan menjadi hal yang sepele.

Saya ingin ID unik untuk membangun ID hashtable -> objek, agar dapat mencari objek yang sudah dilihat. Untuk saat ini saya menyelesaikannya seperti ini:

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
}

Jawaban:


42

Referensinya adalah pengenal unik untuk objek tersebut. Saya tidak tahu cara apa pun untuk mengubahnya menjadi sesuatu seperti string dll. Nilai referensi akan berubah selama pemadatan (seperti yang Anda lihat), tetapi setiap nilai A sebelumnya akan diubah menjadi nilai B, sejauh ini sebagai kode aman yang bersangkutan, itu masih ID unik.

Jika objek yang terlibat berada di bawah kendali Anda, Anda dapat membuat pemetaan menggunakan referensi yang lemah (untuk menghindari pencegahan pengumpulan sampah) dari referensi ke ID yang Anda pilih (GUID, integer, apa pun). Namun, itu akan menambah sejumlah overhead dan kompleksitas.


1
Saya kira untuk pencarian Anda harus mengulang semua referensi yang Anda lacak: WeakReference ke objek yang sama tidak sama satu sama lain, jadi Anda tidak dapat melakukan banyak hal lain.
Roman Starkov

1
Mungkin ada beberapa kegunaan agar setiap objek diberi ID 64-bit yang unik, terutama jika ID tersebut diterbitkan secara berurutan. Saya tidak yakin kegunaannya akan membenarkan biayanya, tetapi hal seperti itu dapat membantu jika seseorang membandingkan dua objek tetap yang berbeda dan menemukan keduanya sama; jika salah satu, bila memungkinkan, menimpa referensi ke yang lebih baru dengan referensi ke yang lebih lama, seseorang dapat menghindari banyak referensi yang berlebihan ke objek yang identik tetapi berbeda.
supercat

1
“Pengenal.” Saya tidak berpikir kata itu berarti apa yang Anda pikirkan.
Slipp D. Thompson

5
@ SlippD.Thompson: Tidak, ini masih hubungan 1-ke-1. Hanya ada satu nilai referensi yang mengacu pada objek tertentu. Nilai itu mungkin muncul berkali-kali dalam memori (misalnya sebagai nilai beberapa variabel), tetapi nilainya masih satu nilai. Ini seperti alamat rumah: Saya bisa menuliskan alamat rumah saya di beberapa lembar kertas, tapi itu tetap pengenal rumah saya. Dua nilai referensi non-identik harus mengacu pada objek yang berbeda - setidaknya di C #.
Jon Skeet

1
@supercat: Saya pikir kita mungkin berbeda dalam pemahaman kita tentang "identitas diringkas" - tapi saya pikir kita juga mungkin tidak membantu siapa pun untuk melangkah lebih jauh dari yang sudah kita miliki :) Hanya salah satu topik yang harus kita diskusikan panjang lebar jika kita pernah bertemu secara langsung ...
Jon Skeet

72

.NET 4 dan yang lebih baru saja

Kabar baik, semuanya!

Alat yang sempurna untuk pekerjaan ini ada di .NET 4 dan disebut ConditionalWeakTable<TKey, TValue>. Kelas ini:

  • dapat digunakan untuk mengasosiasikan data arbitrer dengan instance objek terkelola seperti kamus (meskipun ini bukan kamus)
  • tidak bergantung pada alamat memori, jadi kebal terhadap GC yang memadatkan heap
  • tidak membuat objek tetap hidup hanya karena telah dimasukkan sebagai kunci ke dalam tabel, sehingga dapat digunakan tanpa membuat setiap objek dalam proses Anda hidup selamanya
  • menggunakan persamaan referensi untuk menentukan identitas objek; bergerak, penulis kelas tidak dapat mengubah perilaku ini sehingga dapat digunakan secara konsisten pada objek jenis apa pun
  • dapat diisi dengan cepat, jadi Anda tidak perlu memasukkan kode ke dalam konstruktor objek

5
Hanya untuk kelengkapan: ConditionalWeakTable mengandalkan RuntimeHelpers.GetHashCodedan object.ReferenceEqualsmelakukan pekerjaan batinnya. Perilakunya sama dengan membangun IEqualityComparer<T>yang menggunakan dua metode ini. Jika Anda membutuhkan kinerja, saya benar-benar menyarankan untuk melakukan ini, karena ConditionalWeakTablememiliki kunci di sekitar semua operasinya untuk membuatnya aman untuk utas.
atlaste

1
@StefandeBruijn: A ConditionalWeakTablememegang referensi untuk masing-masingValue yang hanya sekuat referensi yang diadakan di tempat lain yang sesuai Key. Sebuah objek yang ConditionalWeakTablememiliki satu-satunya referensi yang masih ada di mana pun di alam semesta akan secara otomatis tidak ada lagi jika kuncinya ada.
supercat

41

Memeriksa kelas ObjectIDGenerator ? Ini melakukan apa yang Anda coba lakukan, dan apa yang dijelaskan Marc Gravell.

ObjectIDGenerator melacak objek yang diidentifikasi sebelumnya. Saat Anda meminta ID dari suatu objek, ObjectIDGenerator tahu apakah akan mengembalikan ID yang ada, atau menghasilkan dan mengingat ID baru.

ID ini unik selama instance ObjectIDGenerator aktif. Umumnya, masa pakai ObjectIDGenerator berlangsung selama Pemformat yang membuatnya. ID Objek memiliki arti hanya dalam aliran berseri tertentu, dan digunakan untuk melacak objek mana yang memiliki referensi ke objek lain dalam grafik objek berseri.

Menggunakan tabel hash, ObjectIDGenerator mempertahankan ID mana yang ditetapkan ke objek mana. Referensi objek, yang secara unik mengidentifikasi setiap objek, adalah alamat di heap pengumpulan sampah runtime. Nilai referensi objek dapat berubah selama serialisasi, tetapi tabel diperbarui secara otomatis sehingga informasinya benar.

ID Objek adalah angka 64-bit. Alokasi dimulai dari satu, jadi nol tidak pernah menjadi ID objek yang valid. Seorang formatter dapat memilih nilai nol untuk mewakili referensi objek yang nilainya referensi null (Tidak ada dalam Visual Basic).


5
Reflector memberitahu saya bahwa ObjectIDGenerator adalah hashtable yang bergantung pada implementasi GetHashCode default (yaitu tidak menggunakan overload pengguna).
Anton Tykhyy

Mungkin solusi terbaik bila diperlukan ID unik yang dapat dicetak.
Roman Starkov

ObjectIDGenerator juga tidak diterapkan di ponsel.
Anthony Wieser

Saya tidak mengerti persis apa yang dilakukan ObjectIDGenerator tetapi tampaknya berfungsi, bahkan ketika menggunakan RuntimeHelpers.GetHashCode. Saya menguji keduanya dan hanya RuntimeHelpers.GetHashCode yang gagal dalam kasus saya.
Daniel Bişar

+1 - Bekerja sangat apik (setidaknya di desktop).
Hot Licks

37

RuntimeHelpers.GetHashCode()mungkin membantu ( MSDN ).


2
Itu mungkin membantu, tetapi dengan biaya - IIRC, menggunakan objek dasar. GetHashCode () perlu mengalokasikan blok sinkronisasi, yang tidak gratis. Ide yang bagus - +1 dari saya.
Jon Skeet

Terima kasih, saya tidak tahu metode ini. Namun, itu juga tidak menghasilkan kode hash unik (berperilaku persis sama dengan kode contoh dalam pertanyaan). Akan berguna jika pengguna mengganti kode hash, untuk memanggil versi default.
Martin Konicek

1
Anda dapat menggunakan GCHandle jika Anda tidak membutuhkannya terlalu banyak (lihat di bawah).
Anton Tykhyy

42
Sebuah buku tentang .NET oleh penulis yang sangat dihormati menyatakan bahwa RuntimeHelpers.GetHashCode () akan menghasilkan kode yang unik dalam AppDomain, dan Microsoft dapat menamai metode GetUniqueObjectID. Ini salah. Dalam pengujian, saya menemukan bahwa saya biasanya akan mendapatkan duplikat pada saat saya membuat 10.000 instance objek (WinForms TextBox), dan tidak pernah bisa melewati 30.000. Kode yang mengandalkan keunikan yang seharusnya menyebabkan kerusakan intermiten dalam sistem produksi setelah membuat tidak lebih dari 1/10 objek yang banyak itu.
Jan Hettich

3
@supercat: Aha - baru saja menemukan beberapa bukti, dari tahun 2003, yang berasal dari .NET 1.0 dan 1.1. Sepertinya mereka berencana mengubah .NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet

7

Anda dapat mengembangkan hal Anda sendiri dalam sekejap. Misalnya:

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

Anda dapat memilih apa yang ingin Anda miliki sebagai ID unik Anda sendiri, misalnya, System.Guid.NewGuid () atau hanya integer untuk akses tercepat.


2
Tidak akan membantu jika yang Anda butuhkan ini adalah Disposebug, karena ini akan mencegah segala jenis pembuangan.
Roman Starkov

1
Ini tidak cukup berfungsi karena kamus menggunakan persamaan alih-alih identitas, menciutkan objek yang mengembalikan nilai yang sama untuk objek. Sama
Anthony Wieser

1
Ini akan membuat objek tetap hidup.
Martin Lottering

1
@MartinLottering bagaimana jika dia menggunakan ConditionalWeakTable <object, idType>?
Demetris Leptos

7

Bagaimana dengan metode ini:

Setel bidang di objek pertama ke nilai baru. Jika bidang yang sama di objek kedua memiliki nilai yang sama, itu mungkin contoh yang sama. Jika tidak, keluar sebagai berbeda.

Sekarang atur bidang di objek pertama ke nilai baru yang berbeda. Jika bidang yang sama di objek kedua telah berubah ke nilai yang berbeda, itu pasti contoh yang sama.

Jangan lupa untuk menyetel bidang di objek pertama kembali ke nilai aslinya saat keluar.

Masalah?


4

Dimungkinkan untuk membuat pengenal objek unik di Visual Studio: Di jendela tonton, klik kanan variabel objek dan pilih Buat ID Objek dari menu konteks.

Sayangnya, ini adalah langkah manual, dan saya tidak yakin pengenal dapat diakses melalui kode.


Versi Visual Studio apa yang memiliki fitur ini? Misalnya, versi Express?
Peter Mortensen

3

Anda harus menetapkan sendiri pengenal tersebut, secara manual - baik di dalam instance, atau secara eksternal.

Untuk rekaman yang terkait dengan database, kunci utama mungkin berguna (tetapi Anda masih bisa mendapatkan duplikat). Sebagai alternatif, gunakan a Guid, atau pertahankan penghitung Anda sendiri, alokasikan menggunakan Interlocked.Increment(dan buat cukup besar sehingga tidak akan meluap).



1

Informasi yang saya berikan disini bukanlah hal baru, saya hanya menambahkan ini untuk kelengkapan.

Ide kode ini cukup sederhana:

  • Objek membutuhkan ID unik, yang tidak ada di sana secara default. Sebaliknya, kita harus mengandalkan hal terbaik berikutnya, yaitu RuntimeHelpers.GetHashCodememberi kita semacam ID unik
  • Untuk memeriksa keunikan, ini berarti kita perlu menggunakan object.ReferenceEquals
  • Namun, kami masih ingin memiliki ID unik, jadi saya menambahkan GUID, yang menurut definisi unik.
  • Karena saya tidak suka mengunci semuanya jika saya tidak perlu, saya tidak menggunakannya ConditionalWeakTable.

Gabungan, itu akan memberi Anda kode berikut:

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

Untuk menggunakannya, buat instance UniqueIdMapperdan gunakan GUID yang dikembalikannya untuk objek.


Tambahan

Jadi, ada sedikit lagi yang terjadi di sini; izinkan saya menulis sedikit tentang ConditionalWeakTable.

ConditionalWeakTablemelakukan beberapa hal. Yang paling penting adalah ia tidak peduli dengan pengumpul sampah, yaitu: objek yang Anda rujuk dalam tabel ini akan tetap dikumpulkan. Jika Anda mencari objek, pada dasarnya berfungsi sama dengan kamus di atas.

Penasaran bukan? Lagi pula, ketika sebuah objek dikumpulkan oleh GC, ia memeriksa apakah ada referensi ke objek tersebut, dan jika ada, ia mengumpulkannya. Jadi jika ada objek dariConditionalWeakTable , mengapa objek yang direferensikan dikumpulkan?

ConditionalWeakTablemenggunakan trik kecil, yang juga digunakan oleh beberapa struktur .NET lainnya: alih-alih menyimpan referensi ke objek, ia sebenarnya menyimpan IntPtr. Karena itu bukan acuan yang sebenarnya, benda itu bisa dikoleksi.

Jadi, saat ini ada 2 masalah yang harus diatasi. Pertama, objek bisa dipindahkan di heap, jadi apa yang akan kita gunakan sebagai IntPtr? Dan kedua, bagaimana kita tahu bahwa objek memiliki referensi aktif?

  • Objek dapat disematkan di heap, dan penunjuk aslinya dapat disimpan. Saat GC mengenai objek untuk dihapus, GC membongkar dan mengumpulkannya. Namun, itu berarti kita mendapatkan sumber daya yang disematkan, yang bukanlah ide yang baik jika Anda memiliki banyak objek (karena masalah fragmentasi memori). Ini mungkin bukan cara kerjanya.
  • Saat GC memindahkan objek, GC memanggil kembali, yang kemudian dapat memperbarui referensi. Ini mungkin bagaimana penerapannya dilihat dari panggilan eksternal masukDependentHandle - tapi saya yakin ini sedikit lebih canggih.
  • Bukan penunjuk ke objek itu sendiri, tetapi penunjuk dalam daftar semua objek dari GC disimpan. IntPtr adalah indeks atau penunjuk dalam daftar ini. Daftar ini hanya berubah ketika sebuah objek mengubah generasi, di mana sebuah panggilan balik sederhana dapat memperbarui penunjuk. Jika Anda ingat cara kerja Mark & ​​Sweep, ini lebih masuk akal. Tidak ada pin, dan penghapusan seperti sebelumnya. Saya percaya begitulah cara kerjanya DependentHandle.

Solusi terakhir ini memang mengharuskan runtime tidak menggunakan kembali bucket daftar sampai mereka dibebaskan secara eksplisit, dan juga mengharuskan semua objek diambil dengan panggilan ke runtime.

Jika kami menganggap mereka menggunakan solusi ini, kami juga dapat mengatasi masalah kedua. Algoritme Tandai & Sapu melacak objek mana yang telah dikumpulkan; segera setelah dikumpulkan, kami tahu saat ini. Setelah objek memeriksa apakah objek ada di sana, ia memanggil 'Gratis', yang menghapus penunjuk dan entri daftar. Benda itu benar-benar hilang.

Satu hal penting yang perlu diperhatikan pada saat ini adalah bahwa ada yang salah jika ConditionalWeakTablediperbarui di beberapa utas dan jika tidak aman utas. Hasilnya akan menjadi kebocoran memori. Inilah sebabnya mengapa semua panggilan masuk ConditionalWeakTablemelakukan 'kunci' sederhana yang memastikan ini tidak terjadi.

Hal lain yang perlu diperhatikan adalah pembersihan entri harus dilakukan sesekali. Sementara objek sebenarnya akan dibersihkan oleh GC, entri tidak. Inilah mengapa ConditionalWeakTablehanya tumbuh dalam ukuran. Setelah mencapai batas tertentu (ditentukan oleh peluang tabrakan dalam hash), ini memicu a Resize, yang memeriksa apakah objek harus dibersihkan - jika ya, freedipanggil dalam proses GC, menghapus IntPtrpegangan.

Saya percaya ini juga mengapa DependentHandletidak diekspos secara langsung - Anda tidak ingin mengacaukan banyak hal dan akibatnya mendapatkan kebocoran memori. Hal terbaik berikutnya untuk itu adalah WeakReference(yang juga menyimpan IntPtralih - alih objek) - tetapi sayangnya tidak menyertakan aspek 'ketergantungan'.

Yang tersisa adalah Anda bermain-main dengan mekanik, sehingga Anda dapat melihat ketergantungan dalam tindakan. Pastikan untuk memulainya beberapa kali dan lihat hasilnya:

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 ConditionalWeakTablemungkin lebih baik, karena ini hanya akan mempertahankan representasi untuk objek sementara referensi ada padanya. Juga, saya menyarankan bahwa Int64mungkin lebih baik daripada GUID, karena akan memungkinkan objek diberi peringkat yang persisten . Hal-hal seperti itu mungkin berguna dalam skenario penguncian (misalnya, seseorang dapat menghindari kebuntuan jika satu semua kode yang perlu memperoleh banyak kunci melakukannya dalam beberapa urutan yang ditentukan, tetapi untuk bekerja harus ada urutan yang ditentukan).
supercat

@supercat Yakin tentang longs; itu tergantung pada skenario Anda - di f.ex. sistem terdistribusi terkadang lebih berguna untuk bekerja dengan GUIDs. Adapun ConditionalWeakTable: Anda benar; DependentHandlememeriksa keaktifan (CATATAN: hanya jika ukurannya berubah!), yang dapat berguna di sini. Namun, jika Anda membutuhkan kinerja, penguncian dapat menjadi masalah di sana, jadi dalam hal ini mungkin menarik untuk menggunakan ini ... jujur ​​saya pribadi tidak suka penerapannya ConditionalWeakTable, yang mungkin mengarah pada bias saya menggunakan yang sederhana Dictionary- bahkan meskipun kamu benar.
atlaste

Saya sudah lama penasaran tentang bagaimana ConditionalWeakTablesebenarnya cara kerjanya. Fakta bahwa itu hanya memungkinkan item untuk ditambahkan membuat saya berpikir bahwa itu dirancang untuk meminimalkan overhead terkait konkurensi, tetapi saya tidak tahu cara kerjanya secara internal. Saya merasa penasaran bahwa tidak ada DependentHandlepembungkus sederhana yang tidak menggunakan tabel, karena pasti ada saat-saat penting untuk memastikan bahwa satu objek tetap hidup untuk seumur hidup yang lain, tetapi objek terakhir tidak memiliki ruang untuk referensi ke yang pertama.
supercat

@supercat Saya akan memposting tambahan tentang cara kerjanya menurut saya.
atlaste

Tidak ConditionalWeakTablemengizinkan entri yang telah disimpan dalam tabel untuk dimodifikasi. Dengan demikian, saya akan berpikir bahwa ini dapat diimplementasikan dengan aman menggunakan penghalang memori tetapi tidak dengan kunci. Satu-satunya situasi bermasalah adalah jika dua utas mencoba menambahkan kunci yang sama secara bersamaan; yang dapat diatasi dengan meminta metode "tambah" melakukan penghalang memori setelah item ditambahkan, dan kemudian memindai untuk memastikan bahwa tepat satu item memiliki kunci tersebut. Jika beberapa item memiliki kunci yang sama, salah satunya akan dapat diidentifikasi sebagai "pertama", sehingga memungkinkan untuk menghilangkan yang lain.
supercat

0

Jika Anda menulis modul dalam kode Anda sendiri untuk penggunaan tertentu, metode majkinetor MUNGKIN berhasil. Namun ada beberapa masalah.

Pertama , dokumen resmi TIDAK menjamin bahwa GetHashCode()pengembalian sebuah pengenal unik (lihat Object.GetHashCode Method () ):

Anda tidak boleh berasumsi bahwa kode hash yang sama menyiratkan persamaan objek.

Kedua , asumsikan Anda memiliki jumlah objek yang sangat kecil sehingga GetHashCode()akan berfungsi dalam banyak kasus, metode ini dapat diganti oleh beberapa jenis.
Misalnya, Anda menggunakan beberapa kelas C dan itu menimpa GetHashCode()untuk selalu mengembalikan 0. Kemudian setiap objek C akan mendapatkan kode hash yang sama. Sayangnya, Dictionary, HashTabledan beberapa wadah asosiatif lainnya akan membuat menggunakan metode ini:

Kode hash adalah nilai numerik yang digunakan untuk menyisipkan dan mengidentifikasi objek dalam kumpulan berbasis hash seperti kelas Dictionary <TKey, TValue>, kelas Hashtable, atau tipe yang diturunkan dari kelas DictionaryBase. Metode GetHashCode menyediakan kode hash ini untuk algoritme yang memerlukan pemeriksaan cepat terhadap persamaan objek.

Jadi, pendekatan ini memiliki keterbatasan yang besar.

Dan terlebih lagi , bagaimana jika Anda ingin membangun perpustakaan serba guna? Anda tidak hanya tidak dapat mengubah kode sumber dari kelas yang digunakan, tetapi perilakunya juga tidak dapat diprediksi.

Saya menghargai bahwa Jon dan Simon telah memposting jawaban mereka, dan saya akan memposting contoh kode dan saran tentang kinerja di bawah ini.

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

Dalam pengujian saya, ObjectIDGeneratorakan mengeluarkan pengecualian untuk mengeluh bahwa ada terlalu banyak objek saat membuat 10.000.000 objek (10x dari pada kode di atas) di forloop.

Selain itu, hasil benchmarknya adalah ConditionalWeakTableimplementasinya 1,8x lebih cepat daripada ObjectIDGeneratorimplementasinya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.