Identifiant d’object unique .NET

Est-il possible 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 l = new LinkedList(); 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’ID 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) compresse le tas (= déplace les objects = modifie les adresses).

Question relative au débordement de la stack L’ implémentation par défaut de Object.GetHashCode () peut être liée.

Les objects ne sont pas sous mon contrôle car j’accède aux objects dans un programme en cours de débogage à l’aide de l’API du débogueur. Si je contrôlais les objects, append mes propres identifiants uniques serait sortingvial.

Je voulais que l’identifiant unique permettant de créer un object hashtable ID -> puisse rechercher des objects 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’identificateur unique de l’object. Je ne connais aucun moyen de convertir cela en quelque chose comme une 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 comme le code de sécurité est concerné, c’est toujours un identifiant unique.

Si les objects impliqués sont sous votre contrôle, vous pouvez créer un mappage à l’aide de références faibles (pour éviter d’empêcher le nettoyage de la mémoire) d’une référence à un ID de votre choix (GUID, entier, etc.). Cela appendait une certaine quantité de frais généraux et de complexité.

.NET 4 et versions ultérieures uniquement

Bonnes nouvelles tout le monde!

L’outil parfait pour ce travail est construit en .NET 4 et s’appelle ConditionalWeakTable . Cette classe:

  • peut être utilisé pour associer des données arbitraires à des instances d’objects gérés, un peu comme un dictionnaire (bien qu’il ne s’agisse pas d’un dictionnaire)
  • ne dépend pas des adresses mémoire, il est donc immunisé contre le compactage du tas par le CPG
  • ne garde pas les objects en vie simplement parce qu’ils ont été entrés en tant que clés dans la table, de sorte qu’il peut être utilisé sans que tous les objects de votre processus ne soient toujours actifs
  • utilise l’égalité de référence pour déterminer l’identité d’un object; moveover, les auteurs de classes ne peuvent pas modifier ce comportement pour pouvoir l’utiliser de manière cohérente sur des objects de tout type
  • peut être rempli à la volée, ne nécessite donc pas que vous injectez du code à l’intérieur des constructeurs d’objects

Vérifié la classe ObjectIDGenerator ? Cela fait ce que vous essayez de faire et ce que Marc Gravell décrit.

ObjectIDGenerator garde la trace des objects précédemment identifiés. Lorsque vous demandez l’ID d’un object, ObjectIDGenerator sait s’il doit retourner l’ID existant ou générer et mémoriser un nouvel ID.

Les ID sont uniques pour la durée de vie de l’instance ObjectIDGenerator. Généralement, une durée de vie ObjectIDGenerator dure aussi longtemps que le formateur qui l’a créé. Les identifiants d’object n’ont de sens que dans un stream sérialisé donné et sont utilisés pour suivre les objects qui ont des références à d’autres dans le graphe d’objects sérialisés.

À l’aide d’une table de hachage, ObjectIDGenerator conserve quel ID est affecté à quel object. Les références aux objects, qui identifient de manière unique chaque object, sont des adresses dans le tas récupéré lors de l’exécution. Les valeurs de référence d’object peuvent changer pendant la sérialisation, mais la table est automatiquement mise à jour pour que les informations soient correctes.

Les identifiants d’object sont des nombres de 64 bits. L’allocation commence à un, donc zéro n’est jamais un ID d’object valide. Un formateur peut choisir une valeur zéro pour représenter une référence d’object 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(ssortingng[] 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 d = new Dictionary(); 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 ce que vous voulez avoir comme identifiant unique, par exemple, System.Guid.NewGuid () ou simplement un entier pour un access plus rapide.

Que diriez-vous de cette méthode:

Définissez un champ dans le premier object sur une nouvelle valeur. Si le même champ dans le second object a la même valeur, c’est probablement la même instance. Sinon, quittez comme différent.

Définissez maintenant le champ du premier object sur une nouvelle valeur. Si le même champ dans le second object a changé pour la valeur différente, c’est certainement la même instance.

N’oubliez pas de remettre le champ dans le premier object à sa valeur d’origine à la sortie.

Problèmes?

Il est possible de créer un identificateur d’object unique dans Visual Studio: dans la fenêtre de surveillance, cliquez avec le bouton droit de la souris sur la variable d’object et choisissez Créer un ID d’object dans le menu contextuel.

Malheureusement, il s’agit d’une étape manuelle, et je ne pense pas que l’identifiant soit accessible via le code.

Vous devez atsortingbuer vous-même cet identifiant manuellement, soit à l’intérieur de l’instance, soit en externe.

Pour les enregistrements liés à une firebase database, la clé primaire peut être utile (mais vous pouvez toujours obtenir des doublons). Vous pouvez également utiliser un Guid ou conserver votre propre compteur, en utilisant Interlocked.Increment (et le faire en sorte 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 un “identifiant unique” directement, mais combiné avec WeakReferences (et un hashset?) Pourrait vous donner un moyen assez facile de suivre différentes instances.

Les informations que je donne ici ne sont pas nouvelles, je les ai simplement ajoutées pour être complet.

L’idée de ce code est assez simple:

  • Les objects ont besoin d’un identifiant unique, qui n’est pas là par défaut. Au lieu de cela, nous devons nous appuyer sur la meilleure RuntimeHelpers.GetHashCode à savoir RuntimeHelpers.GetHashCode pour obtenir une sorte d’ID unique.
  • Pour vérifier l’unicité, cela implique que nous devons utiliser object.ReferenceEquals
  • Cependant, nous aimerions toujours avoir un identifiant unique, donc j’ai ajouté un GUID , qui est par définition unique.
  • Parce que je n’aime pas tout verrouiller si je ne le dois pas, je n’utilise pas ConditionalWeakTable .

Combiné, cela vous donnera le code suivant:

 public class UniqueIdMapper { private class ObjectEqualityComparer : IEqualityComparer { public bool Equals(object x, object y) { return object.ReferenceEquals(x, y); } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } private Dictionary dict = new Dictionary(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 le GUID renvoyé pour les objects.


Addenda

Donc, il y a un peu plus de choses ici; laissez-moi écrire un peu au sujet de ConditionalWeakTable .

ConditionalWeakTable fait plusieurs choses. La chose la plus importante est qu’elle ne se soucie pas du ramasse-miettes, c’est-à-dire que les objects que vous référencez dans cette table seront collectés indépendamment du résultat. Si vous recherchez un object, il fonctionne essentiellement comme le dictionnaire ci-dessus.

Curieux non? Après tout, lorsqu’un object est collecté par le CPG, il vérifie s’il existe des références à l’object, et s’il y en a, il les collecte. Donc, s’il y a un object du ConditionalWeakTable , pourquoi l’object référencé sera-t-il collecté alors?

ConditionalWeakTable utilise un petit truc, que d’autres structures .NET utilisent également: au lieu de stocker une référence à l’object, il stocke en réalité un IntPtr. Parce que ce n’est pas une référence réelle, l’object peut être collecté.

Donc, à ce stade, il y a 2 problèmes à résoudre. Tout d’abord, les objects peuvent être déplacés sur le tas, alors qu’est-ce que nous utiliserons comme IntPtr? Et deuxièmement, comment soaps-nous que les objects ont une référence active?

  • L’object peut être épinglé sur le tas et son vrai pointeur peut être stocké. Lorsque le GC frappe l’object à supprimer, il le désarchive et le récupère. Cependant, cela signifie que nous obtenons une ressource épinglée, ce qui n’est pas une bonne idée si vous avez beaucoup d’objects (en raison de problèmes de fragmentation de la mémoire). Ce n’est probablement pas comme ça que ça marche.
  • Lorsque le GC déplace un object, il rappelle, ce qui permet de mettre à jour les références. Cela pourrait être la façon dont il est implémenté à en juger par les appels externes dans DependentHandle – mais je pense que c’est un peu plus sophistiqué.
  • Pas le pointeur sur l’object lui-même, mais un pointeur dans la liste de tous les objects du GC est stocké. IntPtr est un index ou un pointeur dans cette liste. La liste ne change que lorsqu’un object change de génération, à quel point un simple rappel peut mettre à jour les pointeurs. Si vous vous rappelez comment Mark & ​​Sweep fonctionne, cela a plus de sens. Il n’y a pas d’épinglage, et le retrait est comme avant. Je crois que c’est comme ça que ça marche dans DependentHandle .

Cette dernière solution requirejs que le moteur d’exécution ne réutilise pas les compartiments de liste tant qu’ils ne sont pas explicitement libérés. De plus, tous les objects doivent être récupérés par un appel à l’environnement 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 conserve la trace des objects collectés. dès qu’il a été collecté, nous soaps à ce stade. Une fois que l’object vérifie si l’object est là, il appelle “Libre”, ce qui supprime le pointeur et l’entrée de liste. L’object est vraiment parti.

Une chose importante à noter à ce stade est que les choses tournent 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 «verrou» qui garantit que cela ne se produit pas.

Une autre chose à noter est que le nettoyage des entrées doit se produire de temps en temps. Bien que les objects réels soient nettoyés par le CPG, les entrées ne le sont pas. C’est la raison pour laquelle la taille de ConditionalWeakTable augmente. Une fois qu’il atteint une certaine limite (déterminée par la possibilité de collision dans le hachage), il déclenche un Resize , qui vérifie si les objects doivent être nettoyés.

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 avoir une fuite de mémoire en conséquence. La meilleure chose à faire pour cela est une WeakReference (qui stocke également un IntPtr place d’un object), mais n’inclut malheureusement pas l’aspect «dépendance».

Il ne vous rest plus qu’à jouer avec les mécaniciens pour voir la dépendance en action. Assurez-vous de le démarrer plusieurs fois et de regarder 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(ssortingng[] args) { var dep = new MyKey(true); // also try passing this to cwt.Add ConditionalWeakTable cwt = new ConditionalWeakTable(); 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 majkinetor a fonctionné. Mais il y a quelques problèmes.

Premièrement , le document officiel ne garantit PAS que GetHashCode() renvoie un identifiant unique (voir Object.GetHashCode, méthode () ):

Vous ne devez pas supposer que des codes de hachage égaux impliquent l’égalité des objects.

Deuxièmement , supposons que vous ayez une très petite quantité d’objects 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 il remplace GetHashCode() pour toujours retourner 0. Ensuite, chaque object de C recevra le même code de hachage. Malheureusement, Dictionary , HashTable et certains autres conteneurs associatifs utiliseront cette méthode:

Un code de hachage est une valeur numérique utilisée pour insérer et identifier un object dans une collection basée sur le hachage, telle que la classe Dictionary , 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 objects.

Donc, cette approche a de grandes limites.

Et encore plus , que faire 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 la performance ci-dessous.

 using System; using System.Diagnostics; using System.Runtime.ComstackrServices; using System.Runtime.Serialization; using System.Collections.Generic; namespace ObjectSet { public interface IObjectSet { ///  check the existence of an object.  ///  true if object is exist, false otherwise.  bool IsExist(object obj); ///  if the object is not in the set, add it in. else do nothing.  ///  true if successfully added, false otherwise.  bool Add(object obj); } public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet { ///  unit test on object set.  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; } } ///  internal representation of the set. (only use the key)  private ConditionalWeakTable objectSet = new ConditionalWeakTable(); ///  used to fill the out parameter of ConditionalWeakTable.TryGetValue().  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 { ///  unit test on object set.  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; } ///  internal representation of the set.  private ObjectIDGenerator idGenerator = new ObjectIDGenerator(); } } 

Dans mon test, ObjectIDGenerator lancera une exception pour se plaindre du fait qu’il y a trop d’objects lors de la création de 10 000 000 objects (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 .