Pregunta

¿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 (bastante 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 complemento 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 accedo a objetos en un programa que se está depurando utilizando 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 crear 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
}
¿Fue útil?

Solución

La referencia es el identificador único para el objeto. No sé de ninguna manera de convertir esto en algo parecido a una cuerda, etc. El valor de la referencia cambiará durante la compactación (como hemos visto), pero cada valor anterior A se cambiará a valor de B, por lo que en la medida de como el código de seguridad se refiere, sigue siendo un identificador único.

Si los objetos involucrados están bajo su control, se puede crear un mapeo usando referencias débiles (para evitar la prevención de la recolección de basura) a partir de una referencia a un documento de identidad de su elección (GUID, número entero, lo que sea). Eso sería añadir una cierta cantidad de sobrecarga y complejidad, sin embargo.

Otros consejos

.NET 4 y posteriores sólo

Buenas noticias, todo el mundo!

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

  • se puede utilizar para asociar datos arbitrarios con instancias de objetos gestionados tanto como un diccionario (aunque es no es un diccionario)
  • no depende de direcciones de memoria, por lo que es inmune a la GC compactar el montón
  • no guarda objetos vivos sólo porque se han introducido como claves en la tabla, por lo que se puede utilizar sin hacer cada objeto en su proceso de vivir para siempre
  • utilizan la igualdad de referencia para determinar la identidad del objeto; moveover, los autores de la clase no se puede modificar este comportamiento para que pueda ser utilizado persistentemente en objetos de cualquier tipo
  • se pueden rellenar sobre la marcha, por lo que no requiere que se inyecta código dentro de los constructores de objetos

ObjectIDGenerator clase? Esto hace lo que usted está tratando de hacer, y lo que Marc Gravell describe.

  

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

     

Los identificadores son únicos para la vida de la instancia ObjectIDGenerator. En general, una vida ObjectIDGenerator dura tanto como el formateador que lo creó. ID de objeto tienen significado sólo dentro de una secuencia serializada dado, y se utilizan para el seguimiento de que los objetos tienen referencias a otros dentro de la gráfica objeto serializado.

     

Uso de una tabla hash, la ObjectIDGenerator retiene la que se asigna ID a la que objeto. Las referencias a objetos, que identifican de forma única cada objeto, son direcciones en el tiempo de ejecución montón de basura-recogida. valores de referencia de objeto pueden cambiar durante la serialización, pero la tabla se actualiza automáticamente para que la información es correcta.

     

identificadores de objeto son números de 64 bits. Asignación inicia desde uno, así que cero no es un identificador de objeto válido. Un formateador puede elegir un valor cero para representar una referencia de objeto cuyo valor es una referencia nula (Nothing en Visual Basic).

RuntimeHelpers.GetHashCode() puede ayudar ( MSDN ).

Usted puede desarrollar su 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 identificación única por su cuenta, por ejemplo, System.Guid.NewGuid () o simplemente entero para el acceso rápido.

¿Qué hay de este método:

Configurar un campo en el primer objeto a un nuevo valor. Si el mismo campo en el segundo objeto tiene el mismo valor, es probable que sea la misma instancia. De lo contrario, salir tan diferente.

Ahora establecer el campo en el primer objeto a un valor nuevo diferente. Si el mismo campo en el segundo objeto ha cambiado a los diferentes valores, es sin duda la misma instancia.

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

Problemas?

Es posible hacer un identificador de objeto único en Visual Studio:. En la ventana de inspección, haga clic en la variable objeto y elija Hacer ID del objeto en el menú contextual

Por desgracia, este es un paso manual, y no creo que el identificador se puede acceder a través de código.

Usted tendría que asignar un identificador como a ti mismo, de forma manual -. Ya sea dentro de la instancia, o externamente

Para los registros relacionados con una base de datos, la clave principal puede ser útil (pero todavía puede obtener duplicados). Como alternativa, utilice una Guid, o mantener su propio contador, usando la asignación de Interlocked.Increment (y que sea lo suficientemente grande que no es probable que se desborde).

Sé que esto ha sido contestada, pero es al menos útil tener en cuenta que puede utilizar:

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

Lo que no le dará un "identificador único" directamente, pero combinado con WeakReferences (y una hashset?) Podría darle una forma bastante fácil de seguir varios casos.

La información que proporciono aquí no es nueva, solo la agregué para que esté completa.

La idea de este código es bastante simple:

  • Los objetos necesitan una identificación única, que no existe de forma predeterminada.En cambio, tenemos que confiar en la mejor opción, que es RuntimeHelpers.GetHashCode para conseguirnos una especie de identificación única
  • Para verificar la unicidad, esto implica que debemos usar object.ReferenceEquals
  • Sin embargo, aún nos gustaría tener una identificación única, así que agregué una GUID, que es por definición único.
  • Porque no me gusta cerrar todo si no es necesario, 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 del UniqueIdMapper y 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.

ConditionalWeakTable hace 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, verifica si hay referencias al objeto y, si las hay, las recopila.Entonces, si hay un objeto del ConditionalWeakTable, Entonces, ¿por qué se recopilará el objeto al que se hace referencia?

ConditionalWeakTable utiliza un pequeño truco, que también utilizan otras estructuras .NET:en lugar de almacenar una referencia al objeto, en realidad almacena un IntPtr.Como esa 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 se puede almacenar su puntero real.Cuando el GC golpea el objeto para eliminarlo, lo desancla y lo recoge.Sin embargo, eso significaría que obtendríamos un recurso anclado, lo cual no es una buena idea si tienes muchos objetos (debido a problemas de fragmentación de la memoria).Probablemente no sea así como funciona.
  • Cuando el GC mueve un objeto, vuelve a llamar, lo que luego puede actualizar las referencias.Así podría ser como se implementa a juzgar por las llamadas externas en 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 cual una simple devolución de llamada puede actualizar los punteros.Si recuerdas cómo funciona Mark & ​​Sweep, esto tiene más sentido.No hay que fijarlo y la extracción es como antes.Creo que así es como funciona en DependentHandle.

Esta última solución requiere que el tiempo de ejecución no reutilice los depósitos de listas 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 utilizan 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 haya recogido, lo sabremos en este momento.Una vez que el objeto comprueba si está allí, llama a 'Gratis', lo que elimina el puntero y la entrada de la lista.El objeto realmente ha desaparecido.

Una cosa importante a tener en cuenta en este punto es que las cosas van terriblemente mal si ConditionalWeakTable se actualiza en varios subprocesos y si no es seguro para subprocesos.El resultado sería una pérdida de memoria.Es por eso que todas las llamadas ConditionalWeakTable haga un 'bloqueo' simple que garantice 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.Esta es la razón por ConditionalWeakTable sólo crece en tamaño.Una vez que alcanza un cierto límite (determinado por la probabilidad de colisión en el hash), desencadena una Resize, que comprueba si es necesario limpiar los objetos; si es así, free se llama en el proceso GC, eliminando el IntPtr manejar.

Creo que esta es también la razón DependentHandle no está expuesto directamente: no querrás alterar las cosas y, como resultado, sufrir una pérdida de memoria.La mejor opción para eso es un WeakReference (que también almacena un IntPtr en lugar 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 observe 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
    }

Si usted está escribiendo un módulo en su propio código para un uso específico, método de majkinetor PRODUCTOS RELACIONADOS han trabajado. Sin embargo, hay algunos problemas.

Primera , el documento oficial hace no garantía de que el GetHashCode() devuelve un identificador único (ver Object.GetHashCode Método () ):

  

Usted no debe asumir que los códigos hash de la misma implican igualdad de objetos.

Segundo , suponga que tiene una muy pequeña cantidad de objetos para que GetHashCode() funcionará en la mayoría de los casos, este método puede ser anulado por algunos tipos.
Por ejemplo, está utilizando alguna clase C y se anula GetHashCode() para volver siempre 0. A continuación, todos los objetos de C obtendrá el mismo código hash. Por desgracia, Dictionary, HashTable y algunos otros contenedores asociativos harán uso de este método:

  

A código hash es un valor numérico que se utiliza para insertar y identificar un objeto en una colección basada-hash como el Dictionary clase, la clase Hashtable, o un tipo de derivado de la clase DictionaryBase. El método GetHashCode proporciona este código hash para algoritmos que necesitan comprobaciones rápidas de igualdad de objetos.

Por lo tanto, este enfoque tiene grandes limitaciones.

y más , lo que si se quiere construir una biblioteca de uso general? No sólo no son capaces de modificar el código fuente de las clases que se utilizan, pero su comportamiento también es impredecible.

aprecio que Jon y Simon han publicado sus respuestas, y voy a poner un ejemplo de código y una sugerencia en 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 a quejarse de que hay demasiados objetos al crear objetos 10.000.000 (10x que en el código anterior) en el bucle for.

Además, el resultado de referencia es que la aplicación ConditionalWeakTable es 1,8 veces más rápido que la aplicación ObjectIDGenerator.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top