Y a-t-il un moyen d'obtenir un identifiant unique d'une instance?
GetHashCode()
est identique pour les deux références pointant vers la même instance. Cependant, deux instances différentes peuvent (assez facilement) obtenir le même code de hachage:
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);
}
J'écris un addin de débogage, et je dois obtenir une sorte d'identifiant pour une référence qui est unique pendant l'exécution du programme.
J'ai déjà réussi à obtenir une adresse interne de l'instance, qui est unique jusqu'à ce que le ramasse-miettes (GC) compacte le tas (= déplace les objets = modifie les adresses).
Stack Overflow question La mise en œuvre par défaut pour Object.GetHashCode () pourrait être liée.
Les objets ne sont pas sous mon contrôle car j'accède aux objets d'un programme en cours de débogage à l'aide de l'API du débogueur. Si je contrôlais les objets, ajouter mes propres identificateurs uniques serait trivial.
Je voulais un identifiant unique pour construire un identifiant de table de hachage -> objet, afin de pouvoir rechercher des objets déjà vus. Pour l'instant je l'ai résolu comme ceci:
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
}
La référence est l'identifiant unique de l'objet. Je ne connais aucun moyen de convertir cela en chaîne, etc. La valeur de la référence changera pendant le compactage (comme vous l'avez vu), mais chaque valeur précédente A sera remplacée par la valeur B, donc en tant que code sécurisé, il s'agit toujours d'un identifiant unique.
Si les objets concernés sont sous votre contrôle, vous pouvez créer un mappage à l'aide de faibles références (pour éviter d'empêcher le garbage collection) d'une référence à un ID de votre choix (GUID, entier, peu importe). Cela ajouterait toutefois une certaine quantité de frais généraux et de complexité.
Bonnes nouvelles tout le monde!
L'outil idéal pour ce travail est construit dans .NET 4 et s'appelle ConditionalWeakTable<TKey, TValue>
. Cette classe:
Vérifié le ObjectIDGenerator class? Cela correspond à ce que vous essayez de faire et à ce que décrit Marc Gravell.
ObjectIDGenerator assure le suivi des objets précédemment identifiés. Lorsque vous demandez l'ID d'un objet, ObjectIDGenerator sait s'il faut renvoyer l'ID existant ou générer et mémoriser un nouvel ID.
Les ID sont uniques pour la vie de l'instance ObjectIDGenerator. En règle générale, une vie ObjectIDGenerator dure aussi longtemps que le formateur qui l'a créé. Les identificateurs d'objet n'ont de signification que dans un flux sérialisé donné et sont utilisés pour suivre les objets référencés par d'autres dans le graphe d'objets sérialisés.
En utilisant une table de hachage, ObjectIDGenerator conserve quel ID est affecté à quel objet. Les références d'objet, qui identifient de manière unique chaque objet, sont des adresses dans le segment de mémoire collecté par la mémoire d'exécution. Les valeurs de référence d'objet peuvent changer pendant la sérialisation, mais la table est mise à jour automatiquement pour que les informations soient correctes.
Les identifiants d'objet sont des nombres de 64 bits. L'allocation commence à un, de sorte que zéro n'est jamais un ID d'objet valide. Un formateur peut choisir une valeur nulle pour représenter une référence d'objet dont la valeur est une référence null (Nothing en Visual Basic).
RuntimeHelpers.GetHashCode()
peut aider ( MSDN ).
Vous pouvez développer votre propre chose en une seconde. Par exemple:
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++;
}
}
Vous pouvez choisir vous-même ce que vous souhaitez avoir comme identifiant unique, par exemple System.Guid.NewGuid () ou simplement un entier pour un accès plus rapide.
Que diriez-vous de cette méthode:
Définissez un champ dans le premier objet avec une nouvelle valeur. Si le même champ dans le deuxième objet a la même valeur, il s'agit probablement de la même instance. Sinon, quittez comme différent.
Définissez maintenant le champ du premier objet sur une nouvelle valeur différente. Si le même champ du deuxième objet a changé de valeur, il s'agit bien de la même instance.
N'oubliez pas de remettre le champ du premier objet à sa valeur d'origine à la sortie.
Problèmes?
Il est possible de créer un identifiant d'objet unique dans Visual Studio: Dans la fenêtre de surveillance, cliquez avec le bouton droit de la souris sur la variable d'objet et choisissez Créer l'ID d'objet dans le menu contextuel.
Malheureusement, il s'agit d'une étape manuelle et je ne pense pas que l'identifiant soit accessible via un code.
Vous devrez attribuer vous-même un tel identifiant, manuellement, soit à l'intérieur de l'instance, soit à l'extérieur.
Pour les enregistrements liés à une base de données, la clé primaire peut être utile (mais vous pouvez toujours obtenir des doublons). Vous pouvez également utiliser une variable Guid
ou conserver votre propre compteur, en utilisant Interlocked.Increment
(et rendez-le suffisamment grand pour qu'il ne risque pas de déborder).
Je sais que cela a été répondu, mais il est au moins utile de noter que vous pouvez utiliser:
http://msdn.Microsoft.com/en-us/library/system.object.referenceequals.aspx
Ce qui ne vous donnera pas directement un "identifiant unique", mais combiné à WeakReferences (et à un hashset?) Pourrait vous donner un moyen assez simple de suivre différentes instances.
Les informations que je donne ici ne sont pas nouvelles, je viens d’ajouter ceci par souci d’exhaustivité.
L'idée de ce code est assez simple:
RuntimeHelpers.GetHashCode
, pour nous obtenir une sorte d'ID unique.object.ReferenceEquals
GUID
, qui est par définition unique.ConditionalWeakTable
.Combinés, cela vous donnera le code suivant:
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;
}
}
Pour l'utiliser, créez une instance de UniqueIdMapper
et utilisez les GUID renvoyés pour les objets.
Addenda
Donc, il y a un peu plus qui se passe ici; laissez-moi écrire un peu sur ConditionalWeakTable
.
ConditionalWeakTable
fait plusieurs choses. La chose la plus importante est qu’il ne se soucie pas du ramasse-miettes, c’est-à-dire que les objets que vous référencez dans cette table seront collectés indépendamment. Si vous recherchez un objet, son fonctionnement est identique à celui du dictionnaire ci-dessus.
Curieux non? Après tout, lorsqu'un objet est collecté par le GC, il vérifie s'il existe des références à l'objet et, le cas échéant, il les collecte. Donc, s'il y a un objet de la ConditionalWeakTable
, pourquoi l'objet référencé sera-t-il collecté alors?
ConditionalWeakTable
utilise une petite astuce, que certaines autres structures .NET utilisent également: au lieu de stocker une référence à l'objet, elle stocke en fait un IntPtr. Parce que ce n'est pas une vraie référence, l'objet peut être collecté.
Donc, à ce stade, il y a 2 problèmes à résoudre. Premièrement, les objets peuvent être déplacés sur le tas, alors qu'utilisons-nous comme IntPtr? Et deuxièmement, comment savons-nous que les objets ont une référence active?
DependentHandle
- mais je crois que c'est légèrement plus sophistiqué.DependentHandle
.Cette dernière solution nécessite que le moteur d'exécution ne réutilise pas les compartiments de la liste tant qu'ils ne sont pas explicitement libérés, et requiert également que tous les objets soient récupérés par un appel au moteur d'exécution.
Si nous supposons qu'ils utilisent cette solution, nous pouvons également résoudre le deuxième problème. L'algorithme Mark & Sweep enregistre les objets collectés. dès qu'il a été collecté, nous le savons à ce stade. Une fois que l'objet vérifie si l'objet est là, il appelle 'Free', ce qui supprime le pointeur et l'entrée de la liste. L'objet est vraiment parti.
Une chose importante à noter à ce stade est que les choses tournent terriblement mal si ConditionalWeakTable
est mis à jour dans plusieurs threads et s'il n'est pas thread-safe. Le résultat serait une fuite de mémoire. C'est pourquoi tous les appels dans ConditionalWeakTable
font un simple "verrouillage" qui garantit que cela ne se produit pas.
Une autre chose à noter est que le nettoyage des entrées doit avoir lieu de temps en temps. Bien que les objets réels soient nettoyés par le CPG, les entrées ne le sont pas. C'est pourquoi ConditionalWeakTable
ne fait que croître. Une fois qu'elle atteint une certaine limite (déterminée par le risque de collision dans le hachage), elle déclenche une Resize
, qui vérifie si les objets doivent être nettoyés. Si c'est le cas, free
est appelé dans le processus de GC, en supprimant le handle IntPtr
.
Je crois que c’est aussi la raison pour laquelle DependentHandle
n’est pas exposé directement. Vous ne voulez pas vous mêler des choses et obtenir une fuite de mémoire. La meilleure chose à faire pour cela est une WeakReference
(qui stocke également une IntPtr
au lieu d'un objet) - mais n'inclut malheureusement pas l'aspect «dépendance».
Il ne vous reste plus qu'à jouer avec les mécaniciens pour voir la dépendance en action. Assurez-vous de le démarrer plusieurs fois et observez les résultats:
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 vous écrivez un module dans votre propre code pour un usage spécifique, la méthode de majkinetorPOURRAIT a fonctionné. Mais il y a quelques problèmes.
First, le document officiel ne PAS garantit que la fonction GetHashCode()
renvoie un identificateur unique (voir Object.GetHashCode, méthode ()):
Vous ne devez pas supposer que des codes de hachage égaux impliquent une égalité d'objet.
Second, supposons que vous ayez une très petite quantité d'objets pour que GetHashCode()
fonctionne dans la plupart des cas, cette méthode peut être remplacée par certains types.
Par exemple, vous utilisez une classe C et remplace GetHashCode()
pour toujours renvoyer 0. Chaque objet de C recevra le même code de hachage . Malheureusement, Dictionary
, HashTable
et certains autres conteneurs associatifs utiliseront ce méthode:
Un code de hachage est une valeur numérique utilisée pour insérer et identifier un objet dans une collection basée sur le hachage, telle que la classe Dictionary <TKey, TValue>, la classe Hashtable ou un type dérivé de la classe DictionaryBase. La méthode GetHashCode fournit ce code de hachage pour les algorithmes nécessitant des vérifications rapides de l'égalité des objets.
Donc, cette approche a de grandes limites.
Et encore plus, que se passe-t-il si vous voulez construire une bibliothèque à usage général? Non seulement vous ne pouvez pas modifier le code source des classes utilisées, mais leur comportement est également imprévisible.
J'apprécie que Jon et Simon aient posté leurs réponses, et je posterai un exemple de code et une suggestion sur les performances ci-dessous.
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();
}
}
Dans mon test, la variable ObjectIDGenerator
lève une exception pour se plaindre du nombre trop élevé d'objets créés lors de la création de 10 000 000 objets (10 fois plus que dans le code ci-dessus) dans la boucle for
.
En outre, le résultat de référence est que l'implémentation ConditionalWeakTable
est 1,8 fois plus rapide que l'implémentation ObjectIDGenerator
.