Question

Existe-t-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 complément de débogage et j'ai besoin d'obtenir une sorte d'identifiant pour une référence unique lors 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 garbage collector (GC) compacte le tas (= déplace les objets = change les adresses).

Question de débordement de pile Implémentation par défaut pour Object.GetHashCode() pourrait être lié.

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 identifiants uniques serait trivial.

Je voulais l'ID unique pour créer un ID de table de hachage -> objet, 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
}
Était-ce utile?

La solution

La mention est l'identificateur unique pour l'objet. Je ne connais aucune façon de convertir cela en quelque chose comme une chaîne, etc. La valeur de la référence changera au cours du compactage (comme vous l'avez vu), mais chaque valeur précédente A sera changé à la valeur B, pour autant code de sécurité est concerné, il est encore un identifiant unique.

Si les objets impliqués sont sous votre contrôle, vous pouvez créer un mappage en utilisant faibles références (pour ne pas empêcher la collecte des ordures) à partir d'une référence à un ID de votre choix (GUID, entier, peu importe). Cela ajouterait un certain montant des frais généraux et de la complexité, cependant.

Autres conseils

.NET 4 et plus tard seulement

De bonnes nouvelles, tout le monde!

L'outil parfait pour ce travail est construit dans .NET 4 et il est appelé ConditionalWeakTable<TKey, TValue> . Cette classe:

  • peut être utilisé pour associer des données arbitraires avec les instances d'objet géré un peu comme un dictionnaire (bien que est pas un dictionnaire)
  • ne dépend pas des adresses de mémoire, est donc à l'abri de la GC compactage du tas
  • ne garde pas des objets vivants juste parce qu'ils ont été saisis en tant que clés dans la table, il peut donc ê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; moveover, auteurs de classe ne peut pas modifier ce comportement de sorte qu'il peut être utilisé toujours sur des objets de tout type
  • peut être rempli à la volée, ne requiert donc pas que vous injectez code à l'intérieur des constructeurs d'objets

la classe Contrôlé ObjectIDGenerator ? Cela fait ce que vous essayez de faire, et ce que Marc Gravell décrit.

  

Le ObjectIDGenerator conserve la trace des objets précédemment identifiés. Lorsque vous demandez l'ID d'un objet, le ObjectIDGenerator sait si pour retourner l'ID existant ou générer et rappelez-vous un nouvel ID.

     

Les ID sont uniques pour la vie de l'instance ObjectIDGenerator. En général, une vie ObjectIDGenerator dure aussi longtemps que le Formatter qui l'a créé. ID d'objet ont un sens que dans un flux sérialisé donné, et sont utilisés pour le suivi des objets qui ont des références à d'autres dans le graphe d'objet sérialisé.

     

En utilisant une table de hachage, le ObjectIDGenerator conserve l'ID est affectée à quel objet. Les références d'objet qui identifient de manière unique chaque objet, sont des adresses dans le tas d'ordures collectées-exécution. Les valeurs de référence de l'objet peuvent changer au cours de sérialisation, mais la table est automatiquement mis à jour si les informations sont correctes.

     

ID d'objet sont des nombres 64 bits. Allocation commence d'un, si nul 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 dans 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 identifiant unique sur votre propre, par exemple, System.Guid.NewGuid () ou simplement entier pour un accès plus rapide.

Que diriez-vous de cette méthode:

Définir un champ dans le premier objet à une nouvelle valeur. Si le même champ dans le deuxième objet a la même valeur, il est probablement la même instance. Dans le cas contraire, la sortie comme différent.

définissez le champ dans le premier objet à une autre nouvelle valeur. Si le même champ dans le deuxième objet a changé à la valeur différente, il est certainement la même instance.

Ne pas oublier de champ situé dans le premier objet à sa valeur d'origine à la sortie.

Problèmes?

Il est possible de faire un identificateur d'objet unique dans Visual Studio. Dans la fenêtre de la montre, cliquez droit sur la variable d'objet et choisissez Faire Object ID dans le menu contextuel

Malheureusement, ceci est une étape manuelle, et je ne crois pas que l'identifiant est accessible via le code.

Vous devez attribuer un tel identifiant vous-même, manuellement -. Soit à l'intérieur de l'instance, ou à l'extérieur

Pour les documents relatifs à une base de données, la clé primaire peut être utile (mais vous pouvez toujours obtenir des doublons). Sinon, utilisez un Guid, ou garder votre propre compteur, l'attribution à l'aide Interlocked.Increment (et le rendre assez grand 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- nous / bibliothèque / 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 les instances diverses.

Les informations que je donne ici ne sont pas nouvelles, je viens de les ajouter par souci d'exhaustivité.

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

  • Les objets ont besoin d'un identifiant unique, qui n'existe pas par défaut.Au lieu de cela, nous devons nous fier à la meilleure solution, à savoir RuntimeHelpers.GetHashCode pour nous obtenir une sorte d'identifiant unique
  • Pour vérifier l'unicité, cela implique que nous devons utiliser object.ReferenceEquals
  • Cependant, nous aimerions toujours avoir un identifiant unique, j'ai donc 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é, 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 du UniqueIdMapper et utilisez les GUID qu'il renvoie pour les objets.


Addenda

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

ConditionalWeakTable fait plusieurs choses.Le plus important est qu'il ne se soucie pas du ramasse-miettes, c'est-à-dire :les objets que vous référencez dans ce tableau seront collectés malgré tout.Si vous recherchez un objet, il fonctionne essentiellement de la même manière que le 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 si c'est le cas, il les collecte.Donc s'il y a un objet du ConditionalWeakTable, pourquoi l'objet référencé sera-t-il alors collecté ?

ConditionalWeakTable utilise une petite astuce, que certaines autres structures .NET utilisent également :au lieu de stocker une référence à l'objet, il stocke en fait un IntPtr.Comme il ne s'agit pas d'une véritable 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'allons-nous utiliser comme IntPtr ?Et deuxièmement, comment savons-nous que les objets ont une référence active ?

  • L'objet peut être épinglé sur le tas et son véritable pointeur peut être stocké.Lorsque le GC atteint l'objet à supprimer, il le détache et le récupère.Cependant, cela signifierait que nous obtiendrions une ressource épinglée, ce qui n'est pas une bonne idée si vous avez beaucoup d'objets (en raison de problèmes de fragmentation de la mémoire).Ce n’est probablement pas ainsi que cela fonctionne.
  • Lorsque le GC déplace un objet, il le rappelle, qui peut alors mettre à jour les références.C'est peut-être ainsi qu'il est implémenté, à en juger par les appels externes dans DependentHandle - mais je pense que c'est légèrement plus sophistiqué.
  • Ce n'est pas le pointeur vers l'objet lui-même qui est stocké, mais un pointeur dans la liste de tous les objets du GC.Le IntPtr est soit un index, soit un pointeur dans cette liste.La liste ne change que lorsqu'un objet change de génération, auquel cas un simple rappel peut mettre à jour les pointeurs.Si vous vous souvenez du fonctionnement de Mark & ​​Sweep, 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 DependentHandle.

Cette dernière solution nécessite que le runtime ne réutilise pas les buckets de liste jusqu'à ce qu'ils soient explicitement libérés, et elle nécessite également que tous les objets soient récupérés par un appel au runtime.

Si nous supposons qu’ils utilisent cette solution, nous pouvons également résoudre le deuxième problème.L'algorithme Mark & ​​Sweep garde une trace des objets qui ont été collectés ;dès qu'il a été collecté, nous le savons à ce stade.Une fois que l'objet vérifie s'il est là, il appelle « Free », ce qui supprime le pointeur et l'entrée de la liste.L'objet a vraiment disparu.

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 ConditionalWeakTable faites 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 GC, les entrées ne le sont pas.C'est pourquoi ConditionalWeakTable ne fait que croître en taille.Une fois qu'il atteint une certaine limite (déterminée par le risque de collision dans le hachage), il déclenche un Resize, qui vérifie si les objets doivent être nettoyés -- si c'est le cas, free est appelé dans le processus GC, supprimant le IntPtr poignée.

Je crois que c'est aussi pour ça DependentHandle n'est pas exposé directement - vous ne voulez pas jouer avec les choses et obtenir une fuite de mémoire en conséquence.La meilleure chose à faire pour cela est un WeakReference (qui stocke également un IntPtr au lieu d'un objet) - mais n'inclut malheureusement pas l'aspect « dépendance ».

Il ne vous reste plus qu'à jouer avec la mécanique, afin de 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(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, méthode de majkinetor peut ont travaillé. Mais il y a quelques problèmes.

Première , le document officiel fait pas garantie que le GetHashCode() retourne un identifiant unique (voir Object.GetHashCode Méthode () ):

  

Vous ne devriez pas supposer que les codes de hachage égale impliquent l'égalité d'objet.

Deuxième , 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 surchargée par certains types.
Par exemple, vous utilisez une classe C et il remplace GetHashCode() de revenir toujours 0. Alors chaque objet de C va obtenir le même code de hachage. Malheureusement, Dictionary, HashTable et d'autres conteneurs associatifs utiliseront cette méthode:

  

Un code de hachage est une valeur numérique qui est utilisé pour insérer et à identifier un objet dans une collection basé sur le hachage tel que le Dictionary classe, la classe Hashtable, ou d'un type dérivé de la classe DictionaryBase. La méthode GetHashCode fournit ce code de hachage pour les algorithmes qui ont besoin d'un contrôle rapide de l'égalité des objets.

Alors, cette approche a de grandes limites.

plus , si vous voulez construire une bibliothèque d'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.

Je comprends que Jon et

scroll top