Identificador de objeto único de .NET

118

¿Existe alguna forma de obtener un identificador único de una instancia?

GetHashCode()es el mismo para las dos referencias que apuntan a la misma instancia. Sin embargo, dos instancias diferentes pueden (con bastante facilidad) obtener el mismo código 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);
}

Estoy escribiendo un complemento de depuración y necesito obtener algún tipo de identificación para una referencia que sea única durante la ejecución del programa.

Ya logré obtener la DIRECCIÓN interna de la instancia, que es única hasta que el recolector de basura (GC) compacta el montón (= mueve los objetos = cambia las direcciones).

Pregunta de desbordamiento de pila La implementación predeterminada para Object.GetHashCode () podría estar relacionada.

Los objetos no están bajo mi control porque estoy accediendo a objetos en un programa que se está depurando usando la API del depurador. Si tuviera el control de los objetos, agregar mis propios identificadores únicos sería trivial.

Quería la ID única para construir una ID de tabla hash -> objeto, para poder buscar objetos ya vistos. Por ahora lo resolví así:

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
}
Martin Konicek
fuente

Respuestas:

42

La referencia es el identificador único del objeto. No conozco ninguna forma de convertir esto en algo como una cadena, etc. El valor de la referencia cambiará durante la compactación (como ha visto), pero cada valor A anterior se cambiará a valor B, hasta ahora en lo que respecta al código seguro, sigue siendo una identificación única.

Si los objetos involucrados están bajo su control, puede crear un mapeo usando referencias débiles (para evitar evitar la recolección de basura) a partir de una referencia a un ID de su elección (GUID, entero, lo que sea). Sin embargo, eso agregaría una cierta cantidad de gastos generales y complejidad.

Jon Skeet
fuente
1
Supongo que para las búsquedas tendrías que iterar sobre todas las referencias que rastreas: WeakReference al mismo objeto no son iguales entre sí, por lo que realmente no puedes hacer mucho más.
Roman Starkov
1
Podría resultar útil tener a cada objeto asignado un ID único de 64 bits, especialmente si dichos ID se emitieron de forma secuencial. No estoy seguro de que la utilidad justifique el costo, pero tal cosa podría ser útil si uno compara dos objetos inmutables distintos y los encuentra iguales; si uno, cuando es posible, sobrescribe la referencia al más nuevo con una referencia al anterior, se puede evitar tener muchas referencias redundantes a objetos idénticos pero distintos.
supercat
1
"Identificador". No creo que esa palabra signifique lo que tú crees que significa.
Slipp D. Thompson
5
@ SlippD.Thompson: No, sigue siendo una relación de 1 a 1. Solo hay un valor de referencia único que se refiere a cualquier objeto dado. Ese valor puede aparecer muchas veces en la memoria (por ejemplo, como el valor de múltiples variables), pero sigue siendo un valor único. Es como la dirección de una casa: puedo escribir la dirección de mi casa en varios en muchas hojas de papel, pero ese sigue siendo el identificador de mi casa. Dos valores de referencia no idénticos deben hacer referencia a objetos diferentes, al menos en C #.
Jon Skeet
1
@supercat: Creo que podemos diferir en nuestra comprensión de las "identidades que se encapsulan", pero creo que probablemente tampoco estemos ayudando a nadie a ir más allá de lo que ya hemos hecho :) Solo uno de los temas que deberíamos discutir extensamente si nos conocemos en persona ...
Jon Skeet
72

.NET 4 y posterior solamente

¡Buenas noticias para todos!

La herramienta perfecta para este trabajo está integrada en .NET 4 y se llama ConditionalWeakTable<TKey, TValue>. Esta clase:

  • se puede usar para asociar datos arbitrarios con instancias de objetos administrados de manera muy similar a un diccionario (aunque no es un diccionario)
  • no depende de las direcciones de memoria, por lo que es inmune a que el GC compacte el montón
  • no mantiene los objetos vivos solo porque se han ingresado como claves en la tabla, por lo que se puede usar sin hacer que cada objeto en su proceso viva para siempre
  • utiliza la igualdad de referencia para determinar la identidad del objeto; Además, los autores de la clase no pueden modificar este comportamiento, por lo que se puede utilizar de forma coherente en objetos de cualquier tipo.
  • se puede rellenar sobre la marcha, por lo que no es necesario inyectar código dentro de los constructores de objetos
Jon
fuente
5
Solo para completar: ConditionalWeakTableconfía en RuntimeHelpers.GetHashCodey object.ReferenceEqualspara hacer su funcionamiento interno. El comportamiento es el mismo que el de construir un IEqualityComparer<T>que utiliza estos dos métodos. Si necesita rendimiento, sugiero que lo haga, ya que ConditionalWeakTabletiene un bloqueo alrededor de todas sus operaciones para que sea seguro para subprocesos.
Atlas
1
@StefandeBruijn: A ConditionalWeakTablecontiene una referencia a cada uno Valueque es tan fuerte como la referencia que se tiene en otro lugar al correspondiente Key. Un objeto al que a ConditionalWeakTabletiene la única referencia existente en cualquier parte del universo dejará de existir automáticamente cuando lo haga la clave.
supercat
41

¿Revisó la clase ObjectIDGenerator ? Esto hace lo que estás intentando hacer y lo que describe Marc Gravell.

ObjectIDGenerator realiza un seguimiento de los objetos previamente identificados. Cuando solicita el ID de un objeto, ObjectIDGenerator sabe si devolver el ID existente o generar y recordar un nuevo ID.

Los ID son únicos durante la vida de la instancia de ObjectIDGenerator. Generalmente, la vida de un ObjectIDGenerator dura tanto como el formateador que lo creó. Los ID de objeto solo tienen significado dentro de un flujo serializado dado y se utilizan para rastrear qué objetos tienen referencias a otros dentro del gráfico de objetos serializados.

Usando una tabla hash, ObjectIDGenerator retiene qué ID está asignado a qué objeto. Las referencias de objeto, que identifican de forma única cada objeto, son direcciones en el montón de recolección de basura en tiempo de ejecución. Los valores de referencia del objeto pueden cambiar durante la serialización, pero la tabla se actualiza automáticamente para que la información sea correcta.

Los ID de objeto son números de 64 bits. La asignación comienza desde uno, por lo que cero nunca es un ID de objeto válido. Un formateador puede elegir un valor cero para representar una referencia de objeto cuyo valor es una referencia nula (Nada en Visual Basic).

hermana
fuente
5
Reflector me dice que ObjectIDGenerator es una tabla hash que se basa en la implementación predeterminada de GetHashCode (es decir, no utiliza sobrecargas de usuarios).
Anton Tykhyy
Probablemente la mejor solución cuando se requieren identificaciones únicas imprimibles.
Roman Starkov
ObjectIDGenerator tampoco está implementado en el teléfono.
Anthony Wieser
No entiendo exactamente qué está haciendo ObjectIDGenerator, pero parece funcionar, incluso cuando usa RuntimeHelpers.GetHashCode. Probé ambos y solo RuntimeHelpers.GetHashCode falla en mi caso.
Daniel Bişar
+1: funciona bastante bien (al menos en el escritorio).
Hot Licks
37

RuntimeHelpers.GetHashCode()puede ayudar ( MSDN ).

Anton Gogolev
fuente
2
Eso puede ayudar, pero con un costo: IIRC, usando el objeto base. GetHashCode () necesita asignar un bloque de sincronización, que no es gratis. Sin embargo, es una buena idea: +1 de mí.
Jon Skeet
Gracias, no conocía este método. Sin embargo, tampoco produce un código hash único (se comporta exactamente igual que el código de muestra de la pregunta). Sin embargo, será útil si el usuario anula el código hash, para llamar a la versión predeterminada.
Martin Konicek
1
Puede usar GCHandle si no necesita muchos de ellos (ver más abajo).
Anton Tykhyy
42
Un libro sobre .NET de un autor muy respetado afirma que RuntimeHelpers.GetHashCode () producirá un código que es único dentro de un AppDomain, y que Microsoft podría haber llamado al método GetUniqueObjectID. Esto simplemente está mal. En las pruebas, descubrí que normalmente obtendría un duplicado cuando había creado 10,000 instancias de un objeto (un WinForms TextBox), y nunca podría pasar de 30,000. El código que se basaba en la supuesta unicidad estaba causando bloqueos intermitentes en un sistema de producción después de crear no más de 1/10 de esa cantidad de objetos.
Jan Hettich
3
@supercat: Ajá, acabo de encontrar algunas pruebas, de 2003, que eran de .NET 1.0 y 1.1. Parece que estaban planeando cambiar para .NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet
7

Puedes desarrollar tu propia cosa en un segundo. Por ejemplo:

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

Puede elegir lo que le gustaría tener como ID único por su cuenta, por ejemplo, System.Guid.NewGuid () o simplemente un número entero para un acceso más rápido.

majkinetor
fuente
2
No ayudará si lo que necesita son Disposeerrores, porque esto evitaría cualquier tipo de eliminación.
Roman Starkov
1
Esto no funciona del todo, ya que el diccionario usa igualdad en lugar de identidad, colapsando objetos que devuelven los mismos valores para el objeto
Anthony Wieser
1
Sin embargo, esto mantendrá vivo el objeto.
Martin Lottering
1
@MartinLottering ¿qué pasa si usa ConditionalWeakTable <object, idType>?
Demetris Leptos
7

¿Qué tal este método?

Establezca un campo en el primer objeto con un nuevo valor. Si el mismo campo en el segundo objeto tiene el mismo valor, probablemente sea la misma instancia. De lo contrario, salga como diferente.

Ahora configure el campo en el primer objeto con un nuevo valor diferente. Si el mismo campo en el segundo objeto ha cambiado a un valor diferente, definitivamente es la misma instancia.

No olvide volver a establecer el campo en el primer objeto a su valor original al salir.

¿Problemas?

perro
fuente
4

Es posible crear un identificador de objeto único en Visual Studio: En la ventana de inspección, haga clic con el botón derecho en la variable de objeto y elija Crear ID de objeto en el menú contextual.

Desafortunadamente, este es un paso manual y no creo que se pueda acceder al identificador mediante un código.

Thomas Bratt
fuente
¿Qué versiones de Visual Studio tienen esta característica? Por ejemplo, ¿las versiones Express?
Peter Mortensen
3

Tendría que asignar dicho identificador usted mismo, manualmente, ya sea dentro de la instancia o externamente.

Para los registros relacionados con una base de datos, la clave principal puede ser útil (pero aún puede obtener duplicados). Alternativamente, use a Guid, o mantenga su propio contador, asignando el uso Interlocked.Increment(y hágalo lo suficientemente grande como para que no se desborde).

Marc Gravell
fuente
1

La información que doy aquí no es nueva, solo agregué esto para completar.

La idea de este código es bastante simple:

  • Los objetos necesitan una identificación única, que no está allí de forma predeterminada. En cambio, tenemos que confiar en la siguiente mejor opción, que es RuntimeHelpers.GetHashCodeconseguirnos una especie de identificación única.
  • Para verificar la unicidad, esto implica que debemos usar object.ReferenceEquals
  • Sin embargo, todavía nos gustaría tener una identificación única, así que agregué una GUID, que es por definición única.
  • Como no me gusta bloquear todo si no tengo que hacerlo, no uso ConditionalWeakTable.

Combinado, eso le dará el siguiente código:

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

Para usarlo, cree una instancia de UniqueIdMappery use los GUID que devuelve para los objetos.


Apéndice

Entonces, hay algo más que hacer aquí; déjame escribir un poco sobre ConditionalWeakTable.

ConditionalWeakTablehace un par de cosas. Lo más importante es que no le importa el recolector de basura, es decir: los objetos a los que hace referencia en esta tabla se recopilarán independientemente. Si busca un objeto, básicamente funciona igual que el diccionario anterior.

¿Curioso no? Después de todo, cuando el GC recopila un objeto, comprueba si hay referencias al objeto y, si las hay, las recopila. Entonces, si hay un objeto del ConditionalWeakTable, ¿por qué se recopilará el objeto al que se hace referencia?

ConditionalWeakTableusa un pequeño truco, que también usan otras estructuras .NET: en lugar de almacenar una referencia al objeto, en realidad almacena un IntPtr. Como no es una referencia real, el objeto se puede recopilar.

Entonces, en este punto hay 2 problemas que abordar. Primero, los objetos se pueden mover en el montón, entonces, ¿qué usaremos como IntPtr? Y segundo, ¿cómo sabemos que los objetos tienen una referencia activa?

  • El objeto se puede anclar en el montón y se puede almacenar su puntero real. Cuando el GC golpea el objeto para retirarlo, lo desencaja y lo recoge. Sin embargo, eso significaría que obtenemos un recurso anclado, lo cual no es una buena idea si tiene muchos objetos (debido a problemas de fragmentación de la memoria). Probablemente no es así como funciona.
  • Cuando el GC mueve un objeto, vuelve a llamar, que luego puede actualizar las referencias. Esta podría ser la forma en que se implementa a juzgar por las llamadas externas DependentHandle, pero creo que es un poco más sofisticado.
  • No se almacena el puntero al objeto en sí, sino un puntero en la lista de todos los objetos del GC. IntPtr es un índice o un puntero en esta lista. La lista solo cambia cuando un objeto cambia de generación, momento en el que una simple devolución de llamada puede actualizar los punteros. Si recuerda cómo funciona Mark & ​​Sweep, esto tiene más sentido. No hay fijación y la eliminación es como antes. Creo que así es como funciona DependentHandle.

Esta última solución requiere que el tiempo de ejecución no reutilice los depósitos de la lista hasta que se liberen explícitamente, y también requiere que todos los objetos se recuperen mediante una llamada al tiempo de ejecución.

Si asumimos que usan esta solución, también podemos abordar el segundo problema. El algoritmo Mark & ​​Sweep realiza un seguimiento de los objetos que se han recopilado; tan pronto como se haya recopilado, lo sabremos en este momento. Una vez que el objeto comprueba si el objeto está allí, llama a 'Libre', lo que elimina el puntero y la entrada de la lista. El objeto realmente se ha ido.

Una cosa importante a tener en cuenta en este punto es que las cosas van terriblemente mal si ConditionalWeakTablese actualiza en varios subprocesos y si no es seguro para subprocesos. El resultado sería una pérdida de memoria. Esta es la razón por la que todas las llamadas ConditionalWeakTablehacen un "bloqueo" simple que garantiza que esto no suceda.

Otra cosa a tener en cuenta es que la limpieza de las entradas debe realizarse de vez en cuando. Si bien el GC limpiará los objetos reales, las entradas no. Es por eso que ConditionalWeakTablesolo crece en tamaño. Una vez que alcanza un cierto límite (determinado por la probabilidad de colisión en el hash), activa un Resize, que verifica si los objetos deben limpiarse; si lo hacen, freese llama en el proceso GC, quitando el IntPtrmango.

Creo que esta es también la razón por la DependentHandleque no se expone directamente: no desea meterse con las cosas y obtener una pérdida de memoria como resultado. La siguiente mejor opción para eso es a WeakReference(que también almacena un en IntPtrlugar de un objeto), pero desafortunadamente no incluye el aspecto de 'dependencia'.

Lo que queda es que juegues con la mecánica, para que puedas ver la dependencia en acción. Asegúrese de iniciarlo varias veces y ver los resultados:

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
    }
atlaste
fuente
1
A ConditionalWeakTablepodría ser mejor, ya que solo conservaría las representaciones de objetos mientras existieran referencias a ellos. Además, sugeriría que un Int64podría ser mejor que un GUID, ya que permitiría que los objetos recibieran un rango persistente . Tales cosas pueden ser útiles en escenarios de bloqueo (por ejemplo, uno puede evitar un punto muerto si todo el código que necesitará adquirir múltiples bloqueos lo hace en algún orden definido, pero para que eso funcione debe haber un orden definido).
supercat
@supercat Seguro sobre el longs; depende de su escenario - en f.ex. sistemas distribuidos a veces es más útil trabajar con GUIDs. En cuanto a ConditionalWeakTable: tienes razón; DependentHandlecomprueba la vitalidad (NOTA: ¡solo cuando la cosa cambia de tamaño!), lo que puede ser útil aquí. Aún así, si necesita rendimiento, el bloqueo puede convertirse en un problema allí, por lo que en ese caso podría ser interesante usar esto ... para ser honesto, personalmente no me gusta la implementación de ConditionalWeakTable, lo que probablemente lleva a mi sesgo de usar un simple Dictionary- incluso aunque tienes razón.
atlaste
Durante mucho tiempo he sentido curiosidad por saber cómo ConditionalWeakTablefunciona realmente. El hecho de que solo permita agregar elementos me hace pensar que está diseñado para minimizar la sobrecarga relacionada con la concurrencia, pero no tengo idea de cómo funciona internamente. Me parece curioso que no haya un DependentHandlecontenedor simple que no use una tabla, ya que definitivamente hay momentos en los que es importante asegurarse de que un objeto se mantenga vivo durante la vida útil de otro, pero el último objeto no tiene espacio para una referencia al primero.
supercat
@supercat Publicaré un apéndice sobre cómo creo que funciona.
Atlas
Las ConditionalWeakTableentradas no permite que se han almacenado en la tabla a modificar. Como tal, creo que podría implementarse de forma segura utilizando barreras de memoria pero no bloqueos. La única situación problemática sería si dos subprocesos intentaran agregar la misma clave simultáneamente; eso podría resolverse haciendo que el método "agregar" realice una barrera de memoria después de agregar un elemento y luego escanee para asegurarse de que exactamente un elemento tenga esa clave. Si varios elementos tienen la misma clave, uno de ellos será identificable como "primero", por lo que será posible eliminar los demás.
supercat
0

Si está escribiendo un módulo en su propio código para un uso específico, el método de majkinetor PODRÍA haber funcionado. Pero hay algunos problemas.

Primero , el documento oficial NO garantiza que GetHashCode()devuelva un identificador único (consulte el método Object.GetHashCode () ):

No debe asumir que los códigos hash iguales implican igualdad de objetos.

En segundo lugar , suponga que tiene una cantidad muy pequeña de objetos para que GetHashCode()funcione en la mayoría de los casos, este método puede ser anulado por algunos tipos.
Por ejemplo, está utilizando alguna clase C y anulaGetHashCode() para devolver siempre 0. Entonces, cada objeto de C obtendrá el mismo código hash. Desafortunadamente, Dictionary, HashTabley algunos otros contenedores asociativos hará uso de este método:

Un código hash es un valor numérico que se utiliza para insertar e identificar un objeto en una colección basada en hash, como la clase Dictionary <TKey, TValue>, la clase Hashtable o un tipo derivado de la clase DictionaryBase. El método GetHashCode proporciona este código hash para algoritmos que necesitan verificaciones rápidas de la igualdad de objetos.

Entonces, este enfoque tiene grandes limitaciones.

Y aun mas , ¿qué sucede si desea crear una biblioteca de uso general? No solo no puede modificar el código fuente de las clases utilizadas, sino que su comportamiento también es impredecible.

Aprecio que Jon y Simon hayan publicado sus respuestas, y publicaré un ejemplo de código y una sugerencia sobre el rendimiento a continuación.

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

En mi prueba, ObjectIDGeneratorlanzará una excepción para quejarse de que hay demasiados objetos al crear 10,000,000 objetos (10 veces más que en el código anterior) en el forciclo.

Además, el resultado de la evaluación comparativa es que la ConditionalWeakTableimplementación es 1.8 veces más rápida que la ObjectIDGeneratorimplementación.

Sr. Ree
fuente