Identificador único de objeto. NET


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

GetHashCode() es lo mismo para las dos referencias que apuntan a la misma instancia. Sin embargo, dos instancias diferentes pueden (muy fácilmente) 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 addin de depuración, y necesito obtener algún tipo de ID 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 Implementación predeterminada para Object.GetHashCode() podría estar relacionado.

Los objetos no están bajo mi control ya que 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 el ID único para construir un objeto hashtable ID ->, para poder buscar ya he visto objetos. 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
}
Author: Community, 2009-04-15

11 answers

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 has visto), pero cada valor anterior A se cambiará a valor B, por lo que en lo que respecta al código seguro, sigue siendo un ID único.

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

 36
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 09:44:40

. NET 4 y posteriores 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 similar a un diccionario (aunque es no es un diccionario)
  • no depende de las direcciones de memoria, por lo que es inmune a la GC compactando el montón
  • no mantiene los objetos vivos solo porque se han introducido como claves en la tabla, por lo que se puede utilizar sin hacer que cada objeto en su proceso viva para siempre
  • usa la igualdad de referencia para determinar la identidad del objeto; moveover, los autores de la clase no pueden modificar este comportamiento para que se pueda usar consistentemente en objetos de cualquier tipo
  • se puede rellenar sobre la marcha, por lo que no requiere que se inyecte código dentro de los constructores de objetos
 56
Author: Jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-26 04:59:13

¿Has comprobado la clase ObjectIDGenerator? Esto hace lo que estás intentando hacer, y lo que Marc Gravell describe.

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

Los IDs son únicos durante la vida útil de la instancia ObjectIDGenerator. Generalmente, la vida de un generador de objetos dura tanto como el Formateador que lo creó. Los ID de objeto solo tienen significado dentro de una secuencia serializada dada, y se usan para rastrear qué objetos tienen referencias a otros dentro del gráfico de objetos serializados.

Usando una tabla hash, el ObjectIDGenerator retiene qué ID se asigna a qué objeto. Las referencias a objetos, que identifican de forma única a cada objeto, son direcciones en el montón recopilado por elementos no utilizados 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).

 36
Author: sisve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 10:58:53

RuntimeHelpers.GetHashCode() puede ayudar ( MSDN ).

 31
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-19 13:50:24

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, el Sistema.Guid.NewGuid () o simplemente integer para un acceso más rápido.

 7
Author: majkinetor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 11:31:25

Qué tal este método:

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

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

No se olvide de establecer el campo en el primer objeto de nuevo a su valor original en salida.

Problemas?

 6
Author: Dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-22 22:03:41

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

Desafortunadamente, este es un paso manual, y no creo que se pueda acceder al identificador a través de un código.

 4
Author: Thomas Bratt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-10 01:38:09

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 primaria puede ser útil (pero aún puede obtener duplicados). Alternativamente, use un Guid, o mantenga su propio contador, asignándolo usando Interlocked.Increment (y hágalo lo suficientemente grande como para que no sea probable que se desborde).

 3
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-15 09:44:52

Sé que esto ha sido contestado, pero es al menos útil notar que puedes usar:

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

Que no le dará un "id único" directamente, sino combinado con WeakReferences (y un hashset?) podría darle una manera bastante fácil de rastrear varias instancias.

 2
Author: Andrew Theken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-20 18:32:47

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

La idea de este código es bastante simple:

  • Los objetos necesitan un ID único, que no existe por defecto. En su lugar, tenemos que confiar en la siguiente mejor cosa, que es RuntimeHelpers.GetHashCode para obtener una especie de ID único
  • Para comprobar la unicidad, esto implica que necesitamos usar object.ReferenceEquals
  • Sin embargo, todavía nos gustaría tener un ID único, por lo que agregué un GUID, que es por definición exclusivo.
  • Porque no me gusta bloquear todo si no tengo que hacerlo, no uso ConditionalWeakTable.

Combinado, que 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 UniqueIdMapper y use los GUID's que devuelve para los objetos.


Adición

Por lo tanto, hay un poco más pasando aquí; permítanme escribir un poco sobre ConditionalWeakTable.

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

Curioso no? Después de todo, cuando un objeto está siendo recogido por el GC, comprueba si hay referencias al objeto, y si las hay, las recopila. Entonces, si hay un objeto de ConditionalWeakTable, ¿por qué se recopilará el objeto referenciado?

ConditionalWeakTable usa un pequeño truco, que algunas otras estructuras. NET también usan: en lugar de almacenar una referencia al objeto, en realidad almacena un IntPtr. Debido a que 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 fijar en el montón, y su puntero real se puede almacenar. Cuando el GC golpea el objeto de eliminación, lo desenrolla y lo recoge. Sin embargo, eso significaría que obtenemos un recurso fijo, lo cual no es una buena idea si tiene muchos objetos (debido a problemas de fragmentación de memoria). Probablemente no es así como funciona.
  • Cuando el GC mueve un objeto, devuelve la llamada, que puede actualizar las referencias. Esto podría ser la forma en que se implementa a juzgar por las llamadas externas en DependentHandle - pero creo que es un poco más sofisticado.
  • No el puntero al objeto en sí, pero se almacena un puntero en la lista de todos los objetos del GC. El 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 recuerdas cómo funciona Mark & Sweep, esto tiene más sentido. No hay fijación, y la eliminación es como era antes. Creo que así es como funciona en DependentHandle.

Esta última solución requiere que el tiempo de ejecución no reutilice los cubos 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 qué objetos se han recopilado; tan pronto como se ha recopilado, sabemos en este punto. Una vez que el objeto comprueba si el objeto está allí, llama a 'Free', que elimina el puntero y la entrada de la lista. El objeto realmente se ha ido.

Una importante lo que hay que tener en cuenta en este punto es que las cosas van terriblemente mal si ConditionalWeakTable se actualiza en varios hilos y si no es seguro hilo. El resultado sería una pérdida de memoria. Esta es la razón por la que todas las llamadas en ConditionalWeakTable hacen un simple 'lock' que asegura que esto no suceda.

Otra cosa a tener en cuenta es que la limpieza de las entradas tiene que ocurrir de vez en cuando. Mientras que los objetos reales serán limpiados por el GC, las entradas no lo son. Es por eso que ConditionalWeakTable solo crece en tamaño. Una vez que alcanza un cierto límite (determinado por chance de colisión en el hash), se activa un Resize, que comprueba si los objetos tienen que ser limpiados if si lo hacen, free se llama en el proceso GC, eliminando el controlador IntPtr.

Creo que esta es también la razón por la que DependentHandle no se expone directamente - no quieres meterte con las cosas y obtener una fuga de memoria como resultado. Lo mejor para eso es un WeakReference (que también almacena un IntPtr en lugar de un objeto), pero desafortunadamente no incluye el aspecto de 'dependencia'.

¿Qué remains es para que juegues con la mecánica, para que puedas ver la dependencia en acción. Ejecútalo 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
    }
 0
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-09 10:14:59

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.

En primer lugar , el documento oficial NO garantiza que el GetHashCode() devuelve un identificador único (véase Objeto.Método GetHashCode()):

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

Segundo , asume que tener 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 reemplaza GetHashCode() para devolver siempre 0. Entonces cada objeto de C obtendrá el mismo código hash. Desafortunadamente, Dictionary, HashTable y algunos otros contenedores asociativos harán 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 Dictionary class, the Hashtable class, or a type derived from the DictionaryBase class. El método GetHashCode proporciona este código hash para algoritmos que necesitan comprobaciones rápidas de la igualdad de objetos.

Por lo tanto, este enfoque tiene grandes limitaciones.

Y aún más, ¿qué pasa si quieres construir una biblioteca de propósito general? No solo no puede modificar el código fuente de las clases utilizadas, sino que su comportamiento también es impredecible.

I agradezco 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, el ObjectIDGenerator lanzará 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 bucle for.

Además, el resultado del punto de referencia es que la implementación de ConditionalWeakTable es 1,8 veces más rápida que la implementación de ObjectIDGenerator.

 0
Author: Mr. Ree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:09