Domanda

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

GetHashCode() è la stessa per i due riferimenti che puntano alla stessa istanza.Tuttavia, due diversi casi (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 debug addin, e ho bisogno di ottenere un qualche tipo di ID per un riferimento unico durante la conduzione del programma.

Sono già riuscito a ottenere un INDIRIZZO interno dell'istanza, che è unica fino a quando il garbage collector (GC) comprime l'heap (= sposta gli oggetti = cambiamenti di indirizzi).

Stack Overflow domanda L'implementazione predefinita per Oggetto.GetHashCode() potrebbero essere correlati.

Gli oggetti non sono sotto il mio controllo, come io sono l'accesso agli oggetti in un programma in fase di debug utilizzando il debugger API.Se fossi in controllo di oggetti, aggiungendo la mia identificatori univoci sarebbe banale.

Volevo un ID univoco per la costruzione di una tabella hash ID -> oggetto, per essere in grado di ricerca già visto oggetti.Per ora ho risolto così:

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
}
È stato utile?

Soluzione

Il riferimento è l'identificatore univoco per l'oggetto. Io non conosco alcun modo di conversione di questo in qualche cosa come una corda, ecc Il valore del riferimento cambierà durante la compattazione (come avete visto), ma ogni valore precedente A sarà cambiato al valore di B, in modo per quanto come codice di sicurezza è interessato è ancora un ID univoco.

Se gli oggetti coinvolti sono sotto il vostro controllo, è possibile creare una mappatura utilizzando riferimenti deboli (per evitare prevenire la raccolta dei rifiuti) da un riferimento a un ID di tua scelta (GUID, intero, a prescindere). Sarebbe aggiungere una certa quantità di costi e la complessità, tuttavia.

Altri suggerimenti

.NET 4 e versioni successive

Buone notizie, 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 con istanze di oggetti gestiti molto simile ad un dizionario (anche se è non un dizionario)
  • non dipende da indirizzi di memoria, quindi è immune al GC compattare il mucchio
  • non tenere oggetti vivi solo perché sono stati inseriti come chiavi nella tabella, in modo che possa essere utilizzato senza fare tutti gli oggetti nel vostro processo di vivere per sempre
  • utilizza l'uguaglianza di riferimento per determinare l'identità dell'oggetto; muovisullink, gli autori di classe non può modificare questo comportamento in modo che possa essere utilizzato sempre su oggetti di qualsiasi tipo
  • può essere popolata al volo, in modo da non richiede che si inietta codice all'interno costruttori oggetto

ObjectIDGenerator classe? Questo fa quello che si sta cercando di fare, e quello che Marc Gravell descrive.

  

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

     

Gli ID sono unici per la vita dell'istanza ObjectIDGenerator. In generale, una vita ObjectIDGenerator dura finché il Formatter che lo ha creato. ID oggetto hanno significato solo all'interno di un dato flusso serializzato, e sono utilizzati per il monitoraggio che gli oggetti hanno riferimenti ad altri all'interno del grafico oggetto serializzato.

     

Utilizzando una tabella hash, l'ObjectIDGenerator mantiene cui ID è assegnato a quale oggetto. I riferimenti a oggetti, che identificano in modo univoco ogni oggetto, sono indirizzi nel runtime garbage collection mucchio. valori di riferimento oggetto possono cambiare durante la serializzazione, ma la tabella viene aggiornata automaticamente in modo le informazioni siano corrette.

     

ID oggetto sono numeri a 64 bit. Allocazione parte da uno, così zero è mai un ID oggetto valido. Un formattatore può scegliere un valore pari a zero per rappresentare un riferimento a un oggetto il cui valore è un riferimento null (Nothing in Visual Basic).

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

È possibile sviluppare le proprie 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++;
        }
    }   

Si può scegliere ciò che si desidera avere ID univoco da soli, per esempio, System.Guid.NewGuid () o semplicemente intero per l'accesso più veloce.

Che ne dite di questo metodo:

Impostare un campo nel primo oggetto ad un nuovo valore. Se lo stesso campo nel secondo oggetto ha lo stesso valore, è probabilmente la stessa istanza. In caso contrario, uscire come diversi.

Ora impostare il campo nel primo oggetto a un altro nuovo valore. Se lo stesso campo nel secondo oggetto è cambiato al diverso valore, è sicuramente la stessa istanza.

Non dimenticare di impostare campo nel primo oggetto di nuovo al suo valore originale in uscita.

Problemi?

E 'possibile fare un identificatore unico oggetto in Visual Studio. Nella finestra di controllo, fare clic con la variabile oggetto e scegliere Fai ID oggetto dal menu di contesto

Purtroppo, questo è un passo manuale, e non credo che l'identificatore si può accedere tramite il codice.

Si dovrà assegnare un tale identificatore te stesso, manualmente -. Sia all'interno dell'istanza, o esternamente

Per i record relativi a un database, la chiave primaria può essere utile (ma è ancora possibile ottenere i duplicati). In alternativa, utilizzare una Guid, o mantenere il proprio contatore, l'assegnazione utilizzando Interlocked.Increment (e renderlo abbastanza grande che non è probabile overflow).

So che questo è stato risposto, ma è almeno utile notare che è possibile utilizzare:

http://msdn.microsoft.com/en- us / library / system.object.referenceequals.aspx

Il che non vi darà un "id unico" direttamente, ma combinato con WeakReferences (e un hashset?) Potrebbe dare un modo piuttosto semplice di tracciare diversi casi.

Le informazioni che ho qui non è nuovo, ho solo aggiunto questo per completezza.

L'idea di questo codice è molto semplice:

  • Oggetti bisogno di un ID univoco, che non c'è di default.Invece, dobbiamo contare sulla prossima cosa migliore, che è RuntimeHelpers.GetHashCode per farci una sorta di ID univoco
  • Per verificare la sua unicità, questo implica che dobbiamo utilizzare object.ReferenceEquals
  • Tuttavia, ci piacerebbe comunque avere un ID univoco, così ho aggiunto un GUID, che è per definizione unica.
  • Perché non mi piace la chiusura di tutto, se non devo, io non lo uso ConditionalWeakTable.

Combinato, che vi darà il codice riportato di seguito:

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 utilizzarlo, creare un'istanza della UniqueIdMapper e utilizzare le GUID restituisce gli oggetti.


Addendum

Quindi, c'è un po ' di più;mi permetta di scrivere un po ' giù per ConditionalWeakTable.

ConditionalWeakTable fa un paio di cose.La cosa più importante è che trucchi non si preoccupano il garbage collector, che è:gli oggetti a cui si fa riferimento in questa tabella saranno raccolti a prescindere.Se si ricerca un oggetto, funziona lo stesso come il dizionario di cui sopra.

Curioso no?Dopo tutto, quando un oggetto sta per essere raccolti dal GC, controlla se ci sono riferimenti all'oggetto, e se ci sono, non li raccoglie.Quindi, se c'è un oggetto dal ConditionalWeakTable, perché l'oggetto di riferimento essere raccolti poi?

ConditionalWeakTable utilizza un piccolo trucco, che alcuni altri .NET strutture, inoltre, utilizzare:invece di memorizzare un riferimento a un oggetto non contiene effettivamente IntPtr.Perché non è un vero punto di riferimento, l'oggetto può essere ritirato.

Quindi, a questo punto ci sono 2 problemi da affrontare.Primo, gli oggetti possono essere spostati nel mucchio, così che potremo utilizzare come IntPtr?E in secondo luogo, come facciamo a sapere che gli oggetti hanno un attivo di riferimento?

  • L'oggetto può essere appuntato sul mucchio, e la sua reale puntatore possono essere memorizzati.Quando il GC colpisce l'oggetto per la rimozione, si unpins e la raccoglie.Tuttavia, significherebbe che abbiamo un appuntato di risorse, che non è una buona idea se si dispone di un sacco di oggetti (a causa di problemi di frammentazione della memoria).Forse questo non è come funziona.
  • Quando il GC si sposta un oggetto, si richiama, che può quindi aggiornare i riferimenti.Questo potrebbe essere come è implementato a giudicare dalle chiamate esterne in DependentHandle - ma io credo che sia un po ' più sofisticati.
  • Non il puntatore all'oggetto in sé, ma un puntatore a lista di tutti gli oggetti dal GC è memorizzato.IntPtr è un indice o un puntatore in questo elenco.La lista cambia solo quando un oggetto cambia generazioni, al punto che un semplice richiamata possibile aggiornare i puntatori.Se vi ricordate come Mark & Sweep opere, questo rende più senso.Non c'è pinning, e la rimozione è come era prima.Credo che questo è come funziona in DependentHandle.

Quest'ultima soluzione richiede che il runtime non ri-utilizzare l'elenco secchi fino a quando non sono esplicitamente liberato, e richiede, inoltre, che tutti gli oggetti sono stati recuperati da una chiamata a runtime.

Se diamo per scontato l'utilizzo di questa soluzione, si può anche risolvere il secondo problema.Il Marchio & Sweep algoritmo tiene traccia di quali oggetti sono stati raccolti;non appena sono stati raccolti, sappiamo a questo punto.Una volta che l'oggetto verifica se l'oggetto c'è, si chiama 'Libero', che rimuove il puntatore del mouse e la voce di elenco.L'oggetto è davvero andato.

Una cosa importante da notare, a questo punto, è che le cose vanno terribilmente male se ConditionalWeakTable viene 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 ConditionalWeakTable fare un semplice 'blocco' che assicura che questo non accada.

Un'altra cosa da notare è che la pulizia delle voci deve accadere una volta in un po'.Mentre gli oggetti reali sarà ripulito dal GC, le voci non sono.Questo è il motivo per cui ConditionalWeakTable solo cresce in dimensioni.Una volta che si raggiunge un certo limite stabilito dalla collisione possibilità in un hash), si innesca un Resize, che controlla se gli oggetti devono essere puliti, se lo fanno, free viene chiamato il GC processo di rimozione del IntPtr la maniglia.

Credo che questo sia anche il motivo per cui DependentHandle non è esposta direttamente - non si vuole pasticciare con le cose e ottenere una perdita di memoria come un risultato.La cosa migliore che è un WeakReference (che memorizza anche un IntPtr invece di un oggetto) ma purtroppo non sono la 'dipendenza' aspetto.

Ciò che resta è il giocattolo in giro con la meccanica, in modo che si può vedere la dipendenza in azione.Assicurarsi di avviare 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
    }

Se si sta scrivendo un modulo nel proprio codice per un utilizzo specifico, il metodo di majkinetor potrebbe hanno lavorato. Ma ci sono alcuni problemi.

prima , il documento ufficiale fa non garanzia che il GetHashCode() restituisce un identificativo univoco (vedi Object.GetHashCode Method () ):

  

Non si deve presumere che i codici hash uguali implicano l'uguaglianza oggetto.

Secondo , si supponga di avere una piccola quantità di oggetti in modo che GetHashCode() funzionerà nella maggior parte dei casi, questo metodo può essere ignorato da alcuni tipi.
Ad esempio, si utilizza un po 'di classe C e le sostituzioni GetHashCode() per tornare sempre 0. Poi ogni oggetto di C otterrà lo stesso codice hash. Purtroppo, Dictionary, HashTable e alcuni altri contenitori associativi faranno uso di questo metodo:

  

Un codice hash è un valore numerico che viene utilizzato per inserire e identificare un oggetto in una collezione hash basato come il Dictionary classe, la classe Hashtable, o un tipo derivato dalla classe DictionaryBase. Il metodo GetHashCode fornisce questo codice hash per algoritmi che necessitano di controlli rapidi di uguaglianza dell'oggetto.

Quindi, questo approccio ha grandi limiti.

e ancora più , che cosa succede se si vuole costruire una libreria di uso generale? Non solo siete in grado di modificare il codice sorgente delle classi utilizzate, ma il loro comportamento è anche imprevedibile.

Mi rendo conto che Jon e Simon hanno inviato le loro risposte, e io postare un esempio di codice e un suggerimento sulle prestazioni di seguito.

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, il ObjectIDGenerator un'eccezione a lamentarsi che ci sono troppi oggetti durante la creazione di oggetti 10.000.000 (10x rispetto al codice di cui sopra), nel ciclo for.

Inoltre, il risultato di riferimento è che l'implementazione ConditionalWeakTable è 1,8 volte più veloce rispetto alla implementazione ObjectIDGenerator.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top