Identificatore di oggetto univoco .NET


118

C'è un modo per ottenere un identificatore univoco di un'istanza?

GetHashCode()è lo stesso per i due riferimenti che puntano alla stessa istanza. Tuttavia, due istanze diverse possono (abbastanza facilmente) ottenere lo stesso codice hash:

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

Sto scrivendo un addin di debug e ho bisogno di ottenere un qualche tipo di ID per un riferimento che sia unico durante l'esecuzione del programma.

Sono già riuscito a ottenere l'INDIRIZZO interno dell'istanza, che è univoco fino a quando il garbage collector (GC) non compatta l'heap (= sposta gli oggetti = cambia gli indirizzi).

Domanda di overflow dello stack L' implementazione predefinita per Object.GetHashCode () potrebbe essere correlata.

Gli oggetti non sono sotto il mio controllo poiché accedo agli oggetti in un programma di cui viene eseguito il debug utilizzando l'API del debugger. Se avessi il controllo degli oggetti, aggiungere i miei identificatori univoci sarebbe banale.

Volevo l'ID univoco per la creazione di un ID tabella hash -> oggetto, per poter cercare oggetti già visti. Per ora l'ho risolto in questo modo:

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
}

Risposte:


42

Il riferimento è l'identificatore univoco dell'oggetto. Non conosco alcun modo per convertirlo in qualcosa di simile a una stringa ecc. Il valore del riferimento cambierà durante la compattazione (come hai visto), ma ogni valore precedente A verrà cambiato in valore B, finora per quanto riguarda il codice di sicurezza, è ancora un ID univoco.

Se gli oggetti coinvolti sono sotto il tuo controllo, potresti creare una mappatura utilizzando riferimenti deboli (per evitare di impedire la garbage collection) da un riferimento a un ID di tua scelta (GUID, intero, qualunque cosa). Ciò tuttavia aggiungerebbe una certa quantità di overhead e complessità.


1
Immagino che per le ricerche dovresti iterare su tutti i riferimenti che traccia: WeakReference allo stesso oggetto non sono uguali tra loro, quindi non puoi fare molto altro.
Roman Starkov

1
Potrebbe essere utile avere a ogni oggetto assegnato un ID univoco a 64 bit, soprattutto se tali ID sono stati emessi in sequenza. Non sono sicuro che l'utilità giustifichi il costo, ma una cosa del genere potrebbe essere utile se si confrontassero due distinti oggetti immutabili e li trovassero uguali; se uno quando possibile sovrascrive il riferimento a quello più recente con un riferimento a quello più vecchio, si può evitare di avere molti riferimenti ridondanti a oggetti identici ma distinti.
supercat

1
“Identifier”. Non credo che quella parola significhi quello che pensi significhi.
Slipp D. Thompson

5
@ SlippD.Thompson: No, è ancora una relazione 1 a 1. C'è solo un singolo valore di riferimento che si riferisce a un dato oggetto. Quel valore può apparire molte volte in memoria (ad esempio come il valore di più variabili), ma è comunque un singolo valore. È come un indirizzo di casa: posso scrivere il mio indirizzo di casa su più fogli di carta, ma è ancora l'identificatore di casa mia. Due valori di riferimento non identici devono fare riferimento a oggetti diversi, almeno in C #.
Jon Skeet

1
@supercat: Penso che potremmo differire nella nostra comprensione delle "identità incapsulate" - ma penso che probabilmente non stiamo aiutando nessuno ad andare oltre quello che abbiamo già :) Solo uno degli argomenti che dovremmo discutere a lungo se ci incontriamo mai di persona ...
Jon Skeet

72

Solo .NET 4 e versioni successive

Buone notizie a tutti!

Lo strumento perfetto per questo lavoro è costruito in .NET 4 e si chiama ConditionalWeakTable<TKey, TValue>. Questa classe:

  • può essere utilizzato per associare dati arbitrari a istanze di oggetti gestiti in modo molto simile a un dizionario (sebbene non sia un dizionario)
  • non dipende dagli indirizzi di memoria, quindi è immune alla compattazione dell'heap da parte del GC
  • non mantiene in vita gli oggetti solo perché sono stati inseriti come chiavi nella tabella, quindi può essere utilizzato senza che ogni oggetto nel tuo processo viva per sempre
  • utilizza l'uguaglianza dei riferimenti per determinare l'identità dell'oggetto; Inoltre, gli autori di classi non possono modificare questo comportamento in modo che possa essere utilizzato in modo coerente su oggetti di qualsiasi tipo
  • può essere popolato al volo, quindi non è necessario inserire codice all'interno dei costruttori di oggetti

5
Solo per completezza: ConditionalWeakTablesi affida RuntimeHelpers.GetHashCodee object.ReferenceEqualsfa i suoi meccanismi interni. Il comportamento è lo stesso della creazione di un oggetto IEqualityComparer<T>che utilizza questi due metodi. Se hai bisogno di prestazioni, in realtà suggerisco di farlo, poiché ConditionalWeakTableha un blocco attorno a tutte le sue operazioni per renderlo sicuro per i thread.
atlante

1
@StefandeBruijn: A ConditionalWeakTablecontiene un riferimento a ciascuno Valueche è forte solo quanto il riferimento tenuto altrove al corrispondente Key. Un oggetto a cui a ConditionalWeakTablecontiene l'unico riferimento esistente ovunque nell'universo cesserà automaticamente di esistere quando la chiave lo farà.
supercat

41

Hai controllato la classe ObjectIDGenerator ? Questo fa quello che stai cercando di fare e quello che descrive Marc Gravell.

ObjectIDGenerator tiene traccia degli oggetti precedentemente identificati. Quando chiedi l'ID di un oggetto, ObjectIDGenerator sa se restituire l'ID esistente o generare e ricordare un nuovo ID.

Gli ID sono univoci per la durata dell'istanza ObjectIDGenerator. In genere, la durata di un ObjectIDGenerator dura quanto il Formatter che lo ha creato. Gli ID oggetto hanno significato solo all'interno di un determinato flusso serializzato e vengono utilizzati per tenere traccia di quali oggetti hanno riferimenti ad altri all'interno del grafo di oggetti serializzato.

Utilizzando una tabella hash, ObjectIDGenerator conserva l'ID assegnato a quale oggetto. I riferimenti agli oggetti, che identificano in modo univoco ogni oggetto, sono indirizzi nell'heap raccolto in modo obsoleto in runtime. I valori di riferimento dell'oggetto possono cambiare durante la serializzazione, ma la tabella viene aggiornata automaticamente in modo che le informazioni siano corrette.

Gli ID oggetto sono numeri a 64 bit. L'allocazione inizia da uno, quindi zero non è mai un ID oggetto valido. Un formattatore può scegliere un valore zero per rappresentare un riferimento a un oggetto il cui valore è un riferimento null (Nothing in Visual Basic).


5
Reflector mi dice che ObjectIDGenerator è una tabella hash che si basa sull'implementazione predefinita di GetHashCode (cioè non utilizza sovraccarichi utente).
Anton Tykhyy

Probabilmente la soluzione migliore quando sono richiesti ID univoci stampabili.
Roman Starkov

ObjectIDGenerator non è implementato neanche sul telefono.
Anthony Wieser

Non capisco esattamente cosa stia facendo ObjectIDGenerator ma sembra funzionare, anche quando utilizza RuntimeHelpers.GetHashCode. Ho testato entrambi e solo RuntimeHelpers.GetHashCode non riesce nel mio caso.
Daniel Bişar

+1 - Funziona abbastanza bene (sul desktop, almeno).
Hot Licks

37

RuntimeHelpers.GetHashCode()può aiutare ( MSDN ).


2
Potrebbe essere d'aiuto, ma con un costo: IIRC, utilizzando l'oggetto di base.GetHashCode () deve allocare un blocco di sincronizzazione, che non è gratuito. Bella idea però - +1 da parte mia.
Jon Skeet

Grazie, non conoscevo questo metodo. Tuttavia, non produce neanche codice hash univoco (si comporta esattamente come il codice di esempio nella domanda). Sarà utile però se l'utente sovrascrive il codice hash, per chiamare la versione predefinita.
Martin Konicek

1
Puoi usare GCHandle se non ne hai bisogno di troppi (vedi sotto).
Anton Tykhyy

42
Un libro su .NET di un autore molto rispettato afferma che RuntimeHelpers.GetHashCode () produrrà un codice univoco all'interno di un AppDomain e che Microsoft avrebbe potuto chiamare il metodo GetUniqueObjectID. Questo è semplicemente sbagliato. Durante i test, ho scoperto che di solito avrei ottenuto un duplicato nel momento in cui avevo creato 10.000 istanze di un oggetto (una casella di testo WinForms) e non avrei mai potuto superare i 30.000. Il codice basato sulla presunta unicità causava arresti anomali intermittenti in un sistema di produzione dopo aver creato non più di 1/10 di molti oggetti.
Jan Hettich

3
@supercat: Aha - ho appena trovato alcune prove, del 2003, che provenivano da .NET 1.0 e 1.1. Sembra che stessero pianificando di cambiare per .NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet

7

Puoi sviluppare le tue cose in un secondo. Per esempio:

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

Puoi scegliere ciò che desideri avere come ID univoco da solo, ad esempio, System.Guid.NewGuid () o semplicemente intero per un accesso più rapido.


2
Non aiuta se ciò di cui hai bisogno sono Disposebug, perché ciò impedirebbe qualsiasi tipo di smaltimento.
Roman Starkov

1
Ciò non funziona perché il dizionario utilizza l'uguaglianza invece dell'identità, collassando oggetti che restituiscono gli stessi valori per object.Equals
Anthony Wieser

1
Ciò manterrà comunque in vita l'oggetto.
Martin Lottering,

1
@MartinLottering e se usasse ConditionalWeakTable <object, idType>?
Demetris Leptos

7

Che ne dici di questo metodo:

Imposta un campo nel primo oggetto su un nuovo valore. Se lo stesso campo nel secondo oggetto ha lo stesso valore, probabilmente è la stessa istanza. Altrimenti, esci come diverso.

Ora imposta il campo nel primo oggetto su un nuovo valore diverso. Se lo stesso campo nel secondo oggetto è cambiato con un valore diverso, è sicuramente la stessa istanza.

Non dimenticare di riportare il campo nel primo oggetto al valore originale all'uscita.

I problemi?


4

È possibile creare un identificatore di oggetto univoco in Visual Studio: nella finestra di controllo, fare clic con il pulsante destro del mouse sulla variabile oggetto e scegliere Crea ID oggetto dal menu di scelta rapida.

Sfortunatamente, questo è un passaggio manuale e non credo che l'identificatore sia accessibile tramite codice.


Quali versioni di Visual Studio dispongono di questa funzionalità? Ad esempio, le versioni Express?
Peter Mortensen

3

Dovresti assegnare tu stesso un tale identificatore, manualmente, all'interno dell'istanza o esternamente.

Per i record relativi a un database, la chiave primaria può essere utile (ma puoi comunque ottenere duplicati). In alternativa, usa a Guid, o mantieni il tuo contatore, allocando usando Interlocked.Increment(e rendilo abbastanza grande da evitare che trabocchi).



1

Le informazioni che fornisco qui non sono nuove, l'ho solo aggiunto per completezza.

L'idea di questo codice è abbastanza semplice:

  • Gli oggetti richiedono un ID univoco, che non è presente per impostazione predefinita. Invece, dobbiamo fare affidamento sulla prossima cosa migliore, che è quella RuntimeHelpers.GetHashCodedi procurarci una sorta di ID univoco
  • Per verificare l'unicità, questo implica che dobbiamo usare object.ReferenceEquals
  • Tuttavia, vorremmo comunque avere un ID univoco, quindi ho aggiunto un GUID, che è per definizione unico.
  • Perché non mi piace bloccare tutto se non devo, non lo uso ConditionalWeakTable.

Combinato, questo ti darà il seguente codice:

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

Per usarlo, creare un'istanza di UniqueIdMappere utilizzare il GUID restituito per gli oggetti.


appendice

Quindi, sta succedendo un po 'di più qui; fammi scrivere un po 'su ConditionalWeakTable.

ConditionalWeakTablefa un paio di cose. La cosa più importante è che non si preoccupi del garbage collector, ovvero: gli oggetti a cui fai riferimento in questa tabella verranno raccolti a prescindere. Se cerchi un oggetto, fondamentalmente funziona come il dizionario sopra.

Curioso no? Dopo tutto, quando un oggetto viene raccolto dal GC, controlla se ci sono riferimenti all'oggetto e, se ci sono, li raccoglie. Quindi, se c'è un oggetto da ConditionalWeakTable, perché l'oggetto di riferimento verrà raccolto?

ConditionalWeakTableutilizza un piccolo trucco, utilizzato anche da altre strutture .NET: invece di memorizzare un riferimento all'oggetto, memorizza effettivamente un IntPtr. Poiché non è un vero riferimento, l'oggetto può essere raccolto.

Quindi, a questo punto ci sono 2 problemi da affrontare. In primo luogo, gli oggetti possono essere spostati sull'heap, quindi cosa useremo come IntPtr? E secondo, come sappiamo che gli oggetti hanno un riferimento attivo?

  • L'oggetto può essere bloccato sull'heap e il suo vero puntatore può essere memorizzato. Quando il GC colpisce l'oggetto per la rimozione, lo apre e lo raccoglie. Tuttavia, ciò significherebbe che avremo una risorsa bloccata, che non è una buona idea se si hanno molti oggetti (a causa di problemi di frammentazione della memoria). Probabilmente non è così che funziona.
  • Quando il GC sposta un oggetto, richiama, che può quindi aggiornare i riferimenti. Questo potrebbe essere il modo in cui viene implementato a giudicare dalle chiamate esterne inDependentHandle , ma credo sia leggermente più sofisticato.
  • Non il puntatore all'oggetto stesso, ma viene memorizzato un puntatore nell'elenco di tutti gli oggetti dal GC. IntPtr è un indice o un puntatore in questo elenco. L'elenco cambia solo quando un oggetto cambia generazioni, a quel punto un semplice callback può aggiornare i puntatori. Se ricordi come funziona Mark & ​​Sweep, questo ha più senso. Non ci sono blocchi e la rimozione è come prima. Credo che sia così che funziona DependentHandle.

Quest'ultima soluzione richiede che il runtime non riutilizzi i bucket dell'elenco fino a quando non vengono liberati esplicitamente e richiede anche che tutti gli oggetti vengano recuperati da una chiamata al runtime.

Se supponiamo che utilizzino questa soluzione, possiamo anche affrontare il secondo problema. L'algoritmo Mark & ​​Sweep tiene traccia di quali oggetti sono stati raccolti; non appena è stato raccolto, lo sappiamo a questo punto. Una volta che l'oggetto controlla se l'oggetto è presente, chiama "Libero", che rimuove il puntatore e la voce dell'elenco. L'oggetto è davvero sparito.

Una cosa importante da notare a questo punto è che le cose vanno orribilmente storte se ConditionalWeakTableviene aggiornato in più thread e se non è thread-safe. Il risultato sarebbe una perdita di memoria. Questo è il motivo per cui tutte le chiamate in entrata ConditionalWeakTablefanno un semplice "blocco" che garantisce che ciò non avvenga.

Un'altra cosa da notare è che la pulizia delle voci deve essere eseguita di tanto in tanto. Mentre gli oggetti effettivi verranno puliti dal GC, le voci non lo sono. Questo è il motivo per cui ConditionalWeakTablecresce solo di dimensioni. Una volta raggiunto un certo limite (determinato dalla possibilità di collisione nell'hash), attiva un Resize, che controlla se gli oggetti devono essere puliti - se lo fanno, freeviene chiamato nel processo GC, rimuovendo la IntPtrmaniglia.

Credo che questo sia anche il motivo per cui DependentHandlenon è esposto direttamente: non vuoi fare confusione con le cose e ottenere come risultato una perdita di memoria. La prossima cosa migliore è a WeakReference(che memorizza anche un IntPtrinvece di un oggetto) - ma sfortunatamente non include l'aspetto della "dipendenza".

Ciò che resta da fare è giocare con la meccanica, così da poter vedere la dipendenza in azione. Assicurati di avviarlo più volte e guarda i risultati:

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 ConditionalWeakTablepotrebbe essere migliore, poiché persisterebbe solo le rappresentazioni per gli oggetti mentre esistevano riferimenti ad essi. Inoltre, suggerirei che un Int64potrebbe essere migliore di un GUID, poiché consentirebbe di assegnare un rango persistente agli oggetti . Queste cose possono essere utili negli scenari di blocco (ad esempio, si può evitare il deadlock se tutto il codice che dovrà acquisire più blocchi lo fa in un ordine definito, ma affinché funzioni ci deve essere un ordine definito).
supercat

@supercat Sure about the longs; dipende dal tuo scenario - in f.ex. sistemi distribuiti a volte è più utile lavorare con GUIDs. Quanto a ConditionalWeakTable: hai ragione; DependentHandlecontrolla la vitalità (NOTA: solo quando la cosa si ridimensiona!), che può essere utile qui. Tuttavia, se hai bisogno di prestazioni, il blocco può diventare un problema lì, quindi in tal caso potrebbe essere interessante usarlo ... ad essere onesto, personalmente non mi piace l'implementazione ConditionalWeakTable, il che probabilmente porta alla mia preferenza di usare un semplice Dictionary- anche anche se hai ragione.
atlante

Sono stato a lungo curioso di sapere come ConditionalWeakTablefunziona effettivamente. Il fatto che consenta solo l'aggiunta di elementi mi fa pensare che sia progettato per ridurre al minimo l'overhead correlato alla concorrenza, ma non ho idea di come funzioni internamente. Trovo curioso che non ci sia un semplice DependentHandleinvolucro che non usi una tabella, poiché ci sono sicuramente momenti in cui è importante assicurarsi che un oggetto sia mantenuto in vita per la vita di un altro, ma quest'ultimo oggetto non ha spazio per un riferimento al primo.
supercat

@supercat Inserirò un addendum su come penso che funzioni.
atlante

Le ConditionalWeakTablevoci non consente che sono stati memorizzati nella tabella da modificare. In quanto tale, penso che potrebbe essere implementato in modo sicuro utilizzando barriere di memoria ma non blocchi. L'unica situazione problematica sarebbe se due thread tentassero di aggiungere la stessa chiave contemporaneamente; che potrebbe essere risolto facendo in modo che il metodo "add" esegua una barriera di memoria dopo che un elemento è stato aggiunto e quindi scansionando per assicurarsi che esattamente un elemento abbia quella chiave. Se più elementi hanno la stessa chiave, uno di essi sarà identificabile come "primo", quindi sarà possibile eliminare gli altri.
supercat

0

Se stai scrivendo un modulo nel tuo codice per un utilizzo specifico, il metodo di majkinetor POTREBBE aver funzionato. Ma ci sono alcuni problemi.

Innanzitutto , il documento ufficiale NON garantisce che GetHashCode()restituisca un identificatore univoco (vedere il metodo Object.GetHashCode () ):

Non dovresti presumere che codici hash uguali implichino l'uguaglianza degli oggetti.

In secondo luogo , supponi di avere una quantità molto piccola di oggetti in modo che GetHashCode()funzionerà nella maggior parte dei casi, questo metodo può essere sovrascritto da alcuni tipi.
Ad esempio, stai usando una classe C e sostituisce GetHashCode()per restituire sempre 0. Quindi ogni oggetto di C riceverà lo stesso codice hash. Purtroppo, Dictionary, HashTablee di alcuni altri contenitori associativi renderanno utilizzare questo metodo:

Un codice hash è un valore numerico utilizzato per inserire e identificare un oggetto in una raccolta basata su hash come la classe Dictionary <TKey, TValue>, la classe Hashtable o un tipo derivato dalla classe DictionaryBase. Il metodo GetHashCode fornisce questo codice hash per gli algoritmi che richiedono controlli rapidi dell'uguaglianza degli oggetti.

Quindi, questo approccio ha grandi limiti.

E ancora di più , cosa succede se si desidera creare una libreria di uso generale? Non solo non sei in grado di modificare il codice sorgente delle classi utilizzate, ma anche il loro comportamento è imprevedibile.

Apprezzo che Jon e Simon abbiano pubblicato le loro risposte e di seguito pubblicherò un esempio di codice e un suggerimento sulle prestazioni.

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

Nel mio test, ObjectIDGeneratorgenererà un'eccezione per lamentarsi del fatto che ci sono troppi oggetti durante la creazione di 10.000.000 di oggetti (10 volte rispetto al codice sopra) nel forciclo.

Inoltre, il risultato del benchmark è che l' ConditionalWeakTableimplementazione è 1,8 volte più veloce ObjectIDGeneratordell'implementazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.