¿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
}
fuente
.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:fuente
ConditionalWeakTable
confía enRuntimeHelpers.GetHashCode
yobject.ReferenceEquals
para hacer su funcionamiento interno. El comportamiento es el mismo que el de construir unIEqualityComparer<T>
que utiliza estos dos métodos. Si necesita rendimiento, sugiero que lo haga, ya queConditionalWeakTable
tiene un bloqueo alrededor de todas sus operaciones para que sea seguro para subprocesos.ConditionalWeakTable
contiene una referencia a cada unoValue
que es tan fuerte como la referencia que se tiene en otro lugar al correspondienteKey
. Un objeto al que aConditionalWeakTable
tiene la única referencia existente en cualquier parte del universo dejará de existir automáticamente cuando lo haga la clave.¿Revisó la clase ObjectIDGenerator ? Esto hace lo que estás intentando hacer y lo que describe Marc Gravell.
fuente
RuntimeHelpers.GetHashCode()
puede ayudar ( MSDN ).fuente
Puedes desarrollar tu propia cosa en un segundo. Por ejemplo:
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.
fuente
Dispose
errores, porque esto evitaría cualquier tipo de eliminación.¿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?
fuente
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.
fuente
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 usoInterlocked.Increment
(y hágalo lo suficientemente grande como para que no se desborde).fuente
Sé que esto ha sido respondido, pero al menos es útil tener en cuenta que puede usar:
http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx
Lo que no le dará una "identificación única" directamente, pero combinado con WeakReferences (¿y un hashset?) Podría brindarle una manera bastante fácil de rastrear varias instancias.
fuente
La información que doy aquí no es nueva, solo agregué esto para completar.
La idea de este código es bastante simple:
RuntimeHelpers.GetHashCode
conseguirnos una especie de identificación única.object.ReferenceEquals
GUID
, que es por definición única.ConditionalWeakTable
.Combinado, eso le dará el siguiente código:
Para usarlo, cree una instancia de
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, 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?ConditionalWeakTable
usa 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?
DependentHandle
, pero creo que es un poco más sofisticado.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
ConditionalWeakTable
se 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 llamadasConditionalWeakTable
hacen 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
ConditionalWeakTable
solo crece en tamaño. Una vez que alcanza un cierto límite (determinado por la probabilidad de colisión en el hash), activa unResize
, que verifica si los objetos deben limpiarse; si lo hacen,free
se llama en el proceso GC, quitando elIntPtr
mango.Creo que esta es también la razón por la
DependentHandle
que 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 aWeakReference
(que también almacena un enIntPtr
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 ver los resultados:
fuente
ConditionalWeakTable
podría ser mejor, ya que solo conservaría las representaciones de objetos mientras existieran referencias a ellos. Además, sugeriría que unInt64
podrí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).long
s; depende de su escenario - en f.ex. sistemas distribuidos a veces es más útil trabajar conGUID
s. En cuanto aConditionalWeakTable
: tienes razón;DependentHandle
comprueba 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 deConditionalWeakTable
, lo que probablemente lleva a mi sesgo de usar un simpleDictionary
- incluso aunque tienes razón.ConditionalWeakTable
funciona 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 unDependentHandle
contenedor 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.ConditionalWeakTable
entradas 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.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 () ):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 anula
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á uso de este método: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.
En mi prueba,
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 elfor
ciclo.Además, el resultado de la evaluación comparativa es que la
ConditionalWeakTable
implementación es 1.8 veces más rápida que laObjectIDGenerator
implementación.fuente