Identificateur d'objet unique .NET

118

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 addin de débogage, et j'ai besoin d'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 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 L' implémentation par défaut pour Object.GetHashCode () peut être liée.

Les objets ne sont pas sous mon contrôle car j'accède aux objets dans 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
}
Martin Konicek
la source

Réponses:

42

La référence est l'identifiant unique de l'objet. 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 changée en valeur B, donc aussi loin en tant que code sécurisé, il s'agit toujours d'un identifiant unique.

Si les objets 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 garbage collection) à partir d'une référence à un ID de votre choix (GUID, entier, peu importe). Cela ajouterait cependant un certain nombre de frais généraux et de complexité.

Jon Skeet
la source
1
Je suppose que pour les recherches, vous devrez parcourir toutes les références que vous suivez: les références faibles au même objet ne sont pas égales les unes aux autres, vous ne pouvez donc pas vraiment faire grand chose d'autre.
Roman Starkov
1
Il pourrait être utile de faire attribuer à chaque objet un identifiant unique de 64 bits, en particulier si ces identifiants étaient émis séquentiellement. Je ne suis pas sûr que l'utilité justifierait le coût, mais une telle chose pourrait être utile si l'on compare deux objets immuables distincts et les trouve égaux; si l'on écrase lorsque cela est possible la référence à la plus récente par une référence à l'ancienne, on peut éviter d'avoir de nombreuses références redondantes à des objets identiques mais distincts.
supercat
1
«Identifiant». Je ne pense pas que ce mot signifie ce que vous pensez qu'il signifie.
Slipp D.Thompson
5
@ SlippD.Thompson: Non, c'est toujours une relation 1 à 1. Il n'y a qu'une seule valeur de référence qui fait référence à un objet donné. Cette valeur peut apparaître plusieurs fois en mémoire (par exemple en tant que valeur de plusieurs variables), mais il s'agit toujours d'une valeur unique. C'est comme une adresse personnelle: je peux écrire mon adresse personnelle sur plusieurs feuilles de papier, mais c'est toujours l'identifiant de ma maison. Deux valeurs de référence non identiques doivent faire référence à des objets différents - au moins en C #.
Jon Skeet
1
@supercat: Je pense que nous pouvons différer dans notre compréhension des "identités encapsulées" - mais je pense que nous n'aidons probablement personne à aller plus loin que ce que nous avons déjà :) Juste l'un des sujets dont nous devrions discuter longuement si nous nous rencontrons jamais en personne ...
Jon Skeet
72

.NET 4 et versions ultérieures uniquement

Bonnes nouvelles tout le monde!

L'outil parfait pour ce travail est intégré à .NET 4 et s'appelle ConditionalWeakTable<TKey, TValue>. Cette classe:

  • peut être utilisé pour associer des données arbitraires à des instances d'objets gérés un peu comme un dictionnaire (bien que ce ne soit pas un dictionnaire)
  • ne dépend pas des adresses mémoire, il est donc immunisé contre le compactage du tas par le GC
  • ne garde pas les objets en vie simplement parce qu'ils ont été saisis comme clés dans la table, il peut donc être utilisé sans que chaque objet de votre processus vive pour toujours
  • utilise l'égalité de référence pour déterminer l'identité de l'objet; De plus, les auteurs de classes ne peuvent pas modifier ce comportement afin qu'il puisse être utilisé de manière cohérente sur des objets de tout type
  • peut être rempli à la volée, il n'est donc pas nécessaire d'injecter du code à l'intérieur des constructeurs d'objets
Jon
la source
5
Juste pour être complet: ConditionalWeakTables'appuie sur RuntimeHelpers.GetHashCodeet object.ReferenceEqualspour faire son fonctionnement interne. Le comportement est le même que la création d'un IEqualityComparer<T>qui utilise ces deux méthodes. Si vous avez besoin de performances, je suggère en fait de le faire, car il ConditionalWeakTablea un verrou autour de toutes ses opérations pour le rendre sûr pour les threads.
atlaste
1
@StefandeBruijn: A ConditionalWeakTabledétient une référence à chacun Valuequi n'est aussi forte que la référence détenue ailleurs au correspondant Key. Un objet auquel a ConditionalWeakTabledétient la seule référence existante n'importe où dans l'univers cessera automatiquement d'exister lorsque la clé le fera.
supercat
41

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

ObjectIDGenerator garde la trace des objets précédemment identifiés. Lorsque vous demandez l'ID d'un objet, l'ObjectIDGenerator sait s'il doit renvoyer 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. En règle générale, la durée de vie d'un ObjectIDGenerator dure aussi longtemps que le formateur qui l'a créé. Les ID d'objet n'ont de signification que dans un flux sérialisé donné et sont utilisés pour suivre quels objets ont des références à d'autres dans le graphe d'objets sérialisés.

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

Les ID d'objet sont des nombres de 64 bits. L'allocation commence par un, donc zéro n'est jamais un ID d'objet valide. Un formateur peut choisir une valeur zéro pour représenter une référence d'objet dont la valeur est une référence null (Nothing en Visual Basic).

sisve
la source
5
Reflector me dit qu'ObjectIDGenerator est une table de hachage reposant sur l'implémentation par défaut de GetHashCode (c'est-à-dire qu'il n'utilise pas de surcharges utilisateur).
Anton Tykhyy
Probablement la meilleure solution lorsque des identifiants uniques imprimables sont nécessaires.
Roman Starkov
ObjectIDGenerator n'est pas non plus implémenté sur le téléphone.
Anthony Wieser
Je ne comprends pas exactement ce que fait ObjectIDGenerator, mais cela semble fonctionner, même lorsqu'il utilise RuntimeHelpers.GetHashCode. J'ai testé les deux et seulement RuntimeHelpers.GetHashCode échoue dans mon cas.
Daniel Bişar
+1 - Fonctionne assez lisse (sur le bureau, au moins).
Hot Licks
37

RuntimeHelpers.GetHashCode()peut aider ( MSDN ).

Anton Gogolev
la source
2
Cela peut bien aider, mais avec un coût - IIRC, en utilisant l'objet de base.GetHashCode () doit allouer un bloc de synchronisation, ce qui n'est pas gratuit. Bonne idée cependant - +1 de ma part.
Jon Skeet
Merci, je ne connaissais pas cette méthode. Cependant, il ne produit pas non plus de code de hachage unique (se comporte exactement comme l'exemple de code dans la question). Sera cependant utile si l'utilisateur remplace le code de hachage, pour appeler la version par défaut.
Martin Konicek
1
Vous pouvez utiliser GCHandle si vous n'en avez pas besoin d'un trop grand nombre (voir ci-dessous).
Anton Tykhyy le
42
Un livre sur .NET par un auteur très respecté déclare que RuntimeHelpers.GetHashCode () produira un code unique dans un AppDomain et que Microsoft aurait pu nommer la méthode GetUniqueObjectID. C'est tout simplement faux. Lors des tests, j'ai constaté que j'obtenais généralement un doublon au moment où j'aurais créé 10000 instances d'un objet (une zone de texte WinForms) et que je ne pourrais jamais dépasser 30000. Le code reposant sur l'unicité supposée provoquait des plantages intermittents dans un système de production après avoir créé pas plus de 1/10 que de nombreux objets.
Jan Hettich
3
@supercat: Aha - je viens de trouver des preuves, de 2003, qui provenaient de .NET 1.0 et 1.1. On dirait qu'ils prévoyaient de changer pour .NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Jon Skeet
7

Vous pouvez développer votre propre truc 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 aimerez avoir comme identifiant unique par vous-même, par exemple, System.Guid.NewGuid () ou simplement un entier pour un accès plus rapide.

majkinetor
la source
2
Cela ne vous aidera pas si vous en avez besoin pour des Disposebogues, car cela empêcherait toute sorte d'élimination.
Roman Starkov
1
Cela ne fonctionne pas tout à fait car le dictionnaire utilise l'égalité au lieu de l'identité, en réduisant les objets qui renvoient les mêmes valeurs pour object.Equals
Anthony Wieser
1
Cela gardera cependant l'objet en vie.
Martin Lottering
1
@MartinLottering et s'il utilise ConditionalWeakTable <object, idType>?
Demetris Leptos
7

Que diriez-vous de cette méthode:

Définissez un champ dans le premier objet sur une nouvelle valeur. Si le même champ dans le deuxième objet a la même valeur, c'est probablement 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 dans le deuxième objet a changé pour la valeur différente, c'est certainement la même instance.

N'oubliez pas de remettre le champ du premier objet à sa valeur d'origine à la sortie.

Problèmes?

Dawg
la source
4

Il est possible de créer un identificateur d'objet unique dans Visual Studio: Dans la fenêtre de surveillance, cliquez avec le bouton droit sur la variable d'objet et choisissez Créer un 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 le code.

Thomas Bratt
la source
Quelles versions de Visual Studio ont cette fonctionnalité? Par exemple, les versions Express?
Peter Mortensen
3

Vous devrez attribuer un tel identifiant vous-même, manuellement - soit à l'intérieur de l'instance, soit en externe.

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 un Guidou conserver votre propre compteur, en allouant en utilisant Interlocked.Increment(et en le rendant suffisamment grand pour qu'il ne déborde pas).

Marc Gravell
la source
1

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 chose suivante, qui est RuntimeHelpers.GetHashCodede 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'ai pas à le faire, je ne l'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 de UniqueIdMapperet utilisez les GUID qu'il renvoie pour les objets.


Addenda

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

ConditionalWeakTablefait plusieurs choses. Le plus important est qu'il ne se soucie pas du ramasse-miettes, c'est-à-dire que 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 s'il y en a, il les collecte. Donc, s'il y a un objet de ConditionalWeakTable, pourquoi l'objet référencé sera-t-il alors collecté?

ConditionalWeakTableutilise une petite astuce, que d'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. 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 une référence active?

  • L'objet peut être épinglé sur le tas et son pointeur réel peut être stocké. Lorsque le GC frappe l'objet à supprimer, il le désépingle et le récupère. Cependant, cela signifierait que nous obtenons 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 rappelle, qui peut alors mettre à jour les références. Cela pourrait être la façon dont il est mis en œuvre à en juger par les appels externes DependentHandle- mais je pense que c'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é. IntPtr est soit un index, soit un pointeur dans cette liste. La liste ne change que lorsqu'un objet change de génération, à quel point 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 la suppression est comme avant. Je pense que c'est ainsi que cela fonctionne DependentHandle.

Cette dernière solution nécessite que le runtime ne réutilise pas les buckets de liste tant qu'ils ne sont pas 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 la trace des objets qui ont été collectés; dès qu'il a été collecté, nous 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 elle ConditionalWeakTableest mise à jour dans plusieurs threads et si elle n'est pas sûre pour les threads. Le résultat serait une fuite de mémoire. C'est pourquoi tous les appels ConditionalWeakTableeffectuent un simple «verrouillage» qui garantit que cela ne se produira pas.

Une autre chose à noter est que le nettoyage des entrées doit avoir lieu de temps en temps. Alors que les objets réels seront nettoyés par le GC, les entrées ne le sont pas. C'est pourquoi ConditionalWeakTablene grandit qu'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 - s'ils le font, freeest appelé dans le processus GC, supprimant la IntPtrpoignée.

Je crois que c'est aussi pourquoi DependentHandlen'est pas exposé directement - vous ne voulez pas gâcher les choses et obtenir une fuite de mémoire en conséquence. La meilleure chose suivante pour cela est a WeakReference(qui stocke également IntPtrun objet au lieu d'un objet) - mais n'inclut malheureusement pas l'aspect «dépendance».

Il ne vous reste plus qu'à jouer avec les mécanismes, afin que vous puissiez 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
    }
goûter
la source
1
A ConditionalWeakTablepourrait être mieux, car il ne ferait que conserver les représentations des objets tant que des références existaient à eux. En outre, je suggérerais qu'un Int64pourrait être meilleur qu'un GUID, car il permettrait aux objets d'obtenir un rang persistant . De telles choses peuvent être utiles dans les scénarios de verrouillage (par exemple, on peut éviter les interblocages si un tout le code qui aura besoin d'acquérir plusieurs verrous le fait dans un ordre défini, mais pour que cela fonctionne, il doit y avoir un ordre défini).
supercat
@supercat Bien sûr pour le longs; cela dépend de votre scénario - par exemple. systèmes distribués, il est parfois plus utile de travailler avec GUIDs. Quant à ConditionalWeakTable: vous avez raison; DependentHandlevérifie la vitalité (NOTE: uniquement lorsque l'objet est redimensionné!), ce qui peut être utile ici. Pourtant, si vous avez besoin de performances, le verrouillage peut devenir un problème là-bas, donc dans ce cas, il pourrait être intéressant de l'utiliser ... pour être honnête, je n'aime pas personnellement la mise en œuvre de ConditionalWeakTable, ce qui conduit probablement à mon parti pris d'utiliser un simple Dictionary- même si vous avez raison.
atlaste
J'ai longtemps été curieux de savoir comment ConditionalWeakTablefonctionne réellement. Le fait qu'il autorise uniquement l'ajout d'éléments me fait penser qu'il est conçu pour minimiser les frais généraux liés à la concurrence, mais je n'ai aucune idée de son fonctionnement en interne. Je trouve curieux qu'il n'y ait pas de simple DependentHandlewrapper qui n'utilise pas de table, car il y a certainement des moments où il est important de s'assurer qu'un objet est maintenu en vie pour la durée de vie d'un autre, mais ce dernier objet n'a pas de place pour une référence au premier.
supercat
@supercat Je publierai un addendum sur la façon dont je pense que cela fonctionne.
atlaste
Le ConditionalWeakTablen'autorise pas la modification des entrées qui ont été stockées dans la table. En tant que tel, je pense qu'il pourrait être implémenté en toute sécurité en utilisant des barrières de mémoire mais pas des verrous. La seule situation problématique serait si deux threads essayaient d'ajouter la même clé simultanément; cela pourrait être résolu en demandant à la méthode «ajouter» d'effectuer une barrière de mémoire après l'ajout d'un élément, puis en effectuant une analyse pour s'assurer qu'exactement un élément possède cette clé. Si plusieurs éléments ont la même clé, l'un d'entre eux sera identifiable comme "premier", il sera donc possible d'éliminer les autres.
supercat le
0

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

Premièrement , le document officiel ne garantit PAS que le GetHashCode()retourne un identifiant unique (voir Object.GetHashCode Method () ):

Vous ne devez pas supposer que les codes de hachage égaux impliquent l'égalité des objets.

Deuxièmement , supposons que vous ayez une très petite quantité d'objets pour que GetHashCode()cela fonctionne dans la plupart des cas, cette méthode peut être remplacée par certains types.
Par exemple, vous utilisez une classe C et elle remplace GetHashCode()toujours 0. Ensuite, chaque objet de C obtiendra le même code de hachage. Malheureusement, Dictionary, HashTableet d'autres conteneurs associatifs feront utiliser cette 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 qui nécessitent des vérifications rapides de l'égalité des objets.

Donc, cette approche a de grandes limites.

Et même plus , que se passe-t-il si vous souhaitez créer 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 publié leurs réponses, et je publierai 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, le ObjectIDGeneratorlancera une exception pour se plaindre qu'il y a trop d'objets lors de la création de 10 000 000 objets (10x que dans le code ci-dessus) dans la forboucle.

En outre, le résultat de référence est que la ConditionalWeakTablemise en œuvre est 1,8 fois plus rapide que la ObjectIDGeneratormise en œuvre.

M. Ree
la source