Identificateur d'objet unique. net

Est-il un moyen d'obtenir un identifiant unique d'une instance?

GetHashCode() est le même 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 j'ai besoin d'obtenir une sorte d'ID pour une référence qui est unique au cours de l'exécution du programme.

j'ai déjà réussi à obtenir L'adresse interne de l'instance, qui est unique jusqu'à ce que le collecteur d'ordures (GC) compacte le tas (= déplace les objets = change les adresses).

Stack Overflow question implémentation par Défaut pour l'Objet.GetHashCode () pourrait être lié.

les objets ne sont pas sous mon contrôle car j'accède à des objets dans un programme en cours de débogage en utilisant l'API de débogage. Si j'avais le contrôle des objets, j'ajouterais le mien les identificateurs uniques seraient insignifiants.

je voulais l'ID unique pour construire un objet ID -> hashtable, pour 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
}
97
demandé sur Community 2009-04-15 13:39:39

11 réponses

la référence est l'identificateur unique de l'objet. Je ne connais aucun moyen de convertir ceci 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 changée en valeur B, donc en ce qui concerne le code de sécurité c'est toujours un ID unique.

si les objets impliqués sont sous votre contrôle, vous pouvez créer un mapping en utilisant références faibles (pour éviter d'empêcher la collecte des ordures) d'une référence à un ID de votre choix (GUID, entier, quoi que ce soit). Cela ajouterait toutefois une certaine quantité de frais généraux et de complexité.

36
répondu Jon Skeet 2009-04-15 09:44:40

.NET 4 et plus tard seulement

bonnes nouvelles, tout le monde!

l'outil idéal pour ce travail est construit dans .NET 4 et il s'appelle ConditionalWeakTable<TKey, TValue> . Cette classe:

  • peut être utilisé pour associer des données arbitraires avec des instances d'objet gérées un peu comme un dictionnaire (bien qu'il soit pas un dictionnaire)
  • ne dépend pas des adresses mémoire, donc est à l'abri de la GC en compactant le tas
  • ne garde pas les objets vivants juste parce qu'ils ont été entrés comme clés dans la table, de sorte qu'il peut être utilisé sans faire chaque objet dans votre processus vivre pour toujours
  • utilise l'égalité de référence pour déterminer l'identité de l'objet; en outre, les auteurs de classe ne peuvent pas modifier ce comportement afin qu'il puisse être utilisé de façon cohérente sur des objets de tout type
  • peut être peuplé sur le fly, donc ne nécessite pas que vous injectez du code à l'intérieur des constructeurs d'objets
56
répondu Jon 2018-03-26 04:59:13

a vérifié la classe ObjectIDGenerator ? Ceci fait ce que vous tentez de faire, et ce que Marc Gravell décrit.

le Générateur D'objectifs garde la trace des objets précédemment identifiés. Lorsque vous demandez L'ID d'un objet, le Générateur D'objectifs sait s'il faut retourner l'ID existant ou générer et mémoriser un nouvel ID.

les IDs sont uniques pour la durée de vie de L'instance ObjectIDGenerator. En général, la vie D'un générateur D'objectifs dure aussi longtemps que le Formateurqui l'a créé. Les ID d'objet ont une signification seulement dans un flux sérialisé donné, et sont utilisés pour suivre quels objets ont des références à d'autres dans le graphique d'objet sérialisé.

à l'aide d'une table de hachage, le Générateur D'objectifs retient quel ID est attribué à quel objet. Les références d'objet, qui identifient chaque objet de façon unique, sont des adresses dans le tas de déchets collectés pendant l'exécution. Référence de l'objet les valeurs peuvent changer pendant la sérialisation, mais la table est mise à jour automatiquement de sorte que l'information est correcte.

les identificateurs D'objet sont des numéros à 64 bits. L'Allocation commence à partir d'un, donc zéro n'est jamais un ID d'objet valide. Un formateurpeut choisir une valeur zéro pour représenter une référence d'objet dont la valeur est une référence nulle (rien dans Visual Basic).

36
répondu sisve 2009-04-15 10:58:53

RuntimeHelpers.GetHashCode() peut aider ( MSDN ).

31
répondu Anton Gogolev 2013-06-19 13:50:24

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 ce que vous voulez avoir comme ID unique sur votre propre, par exemple, Système.GUID.NewGuid () ou simplement entier pour l'accès le plus rapide.

7
répondu majkinetor 2009-04-15 11:31:25

Que pensez-vous de cette méthode:

Définir un champ dans le premier objet à une nouvelle valeur. Si le même champ dans la deuxième objet a la même valeur, c'est probablement la même instance. Sinon, la sortie comme différents.

définit maintenant le champ dans le premier objet à une nouvelle valeur différente. Si le même champ dans le second objet a changé en valeur différente, c'est certainement la même instance.

N'oubliez pas de placer le champ dans le premier objet de retour à sa valeur d'origine à la sortie.

problèmes?

6
répondu Dawg 2011-10-22 22:03:41

il est possible de créer un identifiant d'objet unique dans Visual Studio: dans la fenêtre watch, cliquez avec le bouton droit de la souris sur la variable object et choisissez Make Object ID dans le menu contextuel.

malheureusement, il s'agit d'une étape manuelle, et je ne crois pas que l'identificateur puisse être accédé via le code.

4
répondu Thomas Bratt 2017-03-10 01:38:09

vous devez assigner un tel identifiant vous - même, manuellement-soit à l'intérieur de l'instance, soit à l'extérieur.

pour les enregistrements relatifs à une base de données, la clé primaire peut être utile (mais vous pouvez toujours obtenir des doublons). Alternativement, utilisez un Guid , ou gardez votre propre compteur, allouer en utilisant Interlocked.Increment (et le rendre assez grand qu'il n'est pas susceptible de déborder).

3
répondu Marc Gravell 2009-04-15 09:44:52

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

qui ne vous donnera pas un" identifiant unique " directement, mais combiné avec des références faibles (et un hashset?) pourrait vous donner un moyen assez facile de suivre diverses instances.

2
répondu Andrew Theken 2010-10-20 18:32:47

l'information que je donne ici n'est pas Nouvelle, Je l'ai juste ajouté pour être complet.

l'idée de ce code est assez simple:

  • les objets ont besoin d'un ID unique, qui n'est pas présent par défaut. Au lieu de cela, nous devons compter sur la meilleure chose suivante, qui est RuntimeHelpers.GetHashCode pour nous obtenir une sorte de carte d'identité unique
  • pour vérifier l'unicité, cela implique que nous devons utiliser object.ReferenceEquals
  • Cependant, nous j'aime toujours avoir un ID unique, donc j'ai ajouté un GUID , qui est par définition unique.
  • parce que je n'aime pas tout verrouiller si je n'y suis pas obligé, je n'utilise pas ConditionalWeakTable .

combiné, qui 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éer une instance du UniqueIdMapper et utiliser le GUID qu'il renvoie pour les objets.


Addendum

donc, il se passe un peu plus ici; laissez-moi écrire un peu plus bas sur ConditionalWeakTable .

ConditionalWeakTable fait deux ou trois choses. La chose la plus importante est qu'il ne se soucie pas de l'éboueur, c'est-à-dire: les objets que vous référez dans ce tableau seront recueillis indépendamment. Si vous cherchez un objet, il fonctionne essentiellement la même chose que le dictionnaire ci-dessus.

curieux non? Après tout, lorsqu'un objet est recueilli par le GC, il vérifie s'il y a des références à l'objet et, s'il y en a, il les recueille. Donc s'il y a un objet du ConditionalWeakTable , pourquoi l'objet référencé sera-t-il collecté alors?

ConditionalWeakTable utilise une petite astuce, que d'autres structures .NET utilisent également: au lieu de stocker une référence à l'objet, il stocke en fait un IntPtr. Parce que ce n'est pas une véritable référence, l'objet peut être recueilli.

donc, à ce stade, il y a 2 problèmes à résoudre. Tout d'abord, les objets peuvent être déplacés sur le tas, alors qu'allons-nous utiliser comme IntPtr? Et deuxièmement, comment savons-nous que les objets ont un actif de référence?

  • L'objet peut être épinglé sur le tas, et de son pointeur peut être stockée. Quand le GC frappe l'objet pour le retirer, il le décompose et le recueille. Cependant, cela signifierait que nous obtenons un épinglé des ressources, ce qui n'est pas une bonne idée si vous avez beaucoup des objets (en raison de problèmes de fragmentation de mémoire). Ce n'est probablement pas comme ça que ça marche.
  • lorsque le GC déplace un objet, il rappelle, qui peut alors mettre à jour les références. C'est peut - être ainsi qu'il est mis en œuvre, à en juger par les appels externes dans DependentHandle - mais je pense qu'il est un peu plus sophistiqué.
  • pas le pointeur vers l'objet lui-même, mais un pointeur dans la liste de tous les objets du GC est stocké. Le IntPtr est soit un indice ou d'un pointeur dans cette liste. La liste ne change que lorsqu'un objet change de génération, après quoi 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 l'enlèvement est comme avant. Je crois que c'est comme ça que ça marche dans DependentHandle .

cette dernière solution exige que le runtime ne réutilise pas les seaux de la liste jusqu'à ce qu'ils soient explicitement libérés, et elle exige également que tous les objets sont récupérées par un appel à l'exécution.

Si nous supposons qu'ils utilisent cette solution, nous pouvons également aborder le deuxième problème. L'algorithme Mark & Sweep permet de savoir quels objets ont été collectés; dès qu'ils ont été collectés, nous le savons. 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 liste. L'objet est vraiment passé.

une chose importante à noter à ce point est que les choses vont horriblement mal si ConditionalWeakTable est mis à jour dans plusieurs threads et s'il n'est pas sécurisé. Il en résulterait une fuite de mémoire. C'est pourquoi tous les appels dans ConditionalWeakTable font un simple "lock" qui assure que cela n'arrive pas.

une autre chose à noter est que le nettoyage des entrées doit se faire de temps en temps. Alors que les objets réels seront nettoyés par le GC, les entrées ne sont pas. C'est pourquoi ConditionalWeakTable ne croît en taille. Une fois qu'il atteint une certaine limite (déterminé par le risque de collision dans le hachage), il déclenche un Resize , qui vérifie si les objets doivent être nettoyés -- s'ils le font, free est appelé dans le processus GC, en enlevant la poignée IntPtr .

je crois que c'est aussi pourquoi DependentHandle n'est pas exposé directement - vous ne voulez pas salir avec les choses et une fuite de mémoire. La prochaine meilleure chose, c'est un WeakReference (qui stocke également un IntPtr au lieu d'un objet) - mais malheureusement, il n'inclut pas l'aspect "dépendance".

il vous reste à jouer avec la mécanique, pour que vous puissiez voir la dépendance en action. Assurez-vous de le démarrer plusieurs fois et regardez 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
    }
0
répondu atlaste 2014-01-09 10:14:59

si vous écrivez un module dans votre propre code pour un usage spécifique, méthode de majkinetor pourrait ont fonctionné. Mais il y a quelques problèmes.

premier , le document officiel ne pas garantie que le GetHashCode() renvoie un identifiant unique (voir objet.Méthode GetHashCode() ):

vous ne devez pas supposer que les codes de hachage égaux impliquent l'égalité d'objet.

Second , supposons que vous avez une très petite quantité d'objets de sorte que GetHashCode() fonctionnera dans la plupart des cas, cette méthode peut être dépassée par certains types.

Par exemple, vous utilisez une Classe C et elle remplace GetHashCode() pour toujours retourner 0. Alors chaque objet de C aura le même hachage code. Malheureusement, Dictionary , HashTable et quelques autres conteneurs associatifs feront usage de cette méthode:

un code de hachage est une valeur numérique qui est utilisée pour insérer et identifier un objet dans une collection basée sur le hachage tel que le dictionnaire class, la classe de hachage, ou un type dérivé de la classe DictionaryBase. La méthode GetHashCode fournit ce code de hachage pour les algorithmes qui ont besoin de vérifications rapides de l'égalité des objets.

Donc, cette approche a de grandes limites.

Et encore plus , si vous voulez construire une bibliothèque d'intérêt général? Non seulement vous serez en mesure de modifier le code source des classes utilisées, mais leur comportement est imprévisible.

j'apprécie le fait que Jon et Simon ont publié leurs réponses, et je vais poster un exemple de code et un suggestion sur la performance 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, le ObjectIDGenerator lancera une exception pour se plaindre qu'il y a trop d'objets lors de la création de 10.000.000 d'objets (10x que dans le code ci-dessus) dans la boucle for .

en outre, le résultat de référence est que la mise en œuvre de ConditionalWeakTable est 1,8 fois plus rapide que la mise en œuvre de ObjectIDGenerator .

0
répondu Mr. Ree 2017-05-23 12:18:09