.NET eindeutige Objektkennung

118

Gibt es eine Möglichkeit, eine eindeutige Kennung einer Instanz zu erhalten?

GetHashCode()ist für die beiden Referenzen, die auf dieselbe Instanz verweisen, gleich. Zwei verschiedene Instanzen können jedoch (ziemlich leicht) denselben Hash-Code erhalten:

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);
}

Ich schreibe ein Debugging-Add-In und benötige eine ID für eine Referenz, die während der Programmausführung eindeutig ist.

Ich habe es bereits geschafft, eine interne ADRESSE der Instanz zu erhalten, die eindeutig ist, bis der Garbage Collector (GC) den Heap komprimiert (= verschiebt die Objekte = ändert die Adressen).

Frage zum Stapelüberlauf Die Standardimplementierung für Object.GetHashCode () ist möglicherweise verwandt.

Die Objekte sind nicht unter meiner Kontrolle, da ich über die Debugger-API auf Objekte in einem Programm zugreife, das debuggt wird. Wenn ich die Kontrolle über die Objekte hätte, wäre es trivial, meine eigenen eindeutigen Bezeichner hinzuzufügen.

Ich wollte die eindeutige ID zum Erstellen einer Hashtabellen-ID -> Objekt, um bereits gesehene Objekte nachschlagen zu können. Im Moment habe ich es so gelöst:

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
quelle

Antworten:

42

Die Referenz ist die eindeutige Kennung für das Objekt. Ich kenne keine Möglichkeit, dies in eine Zeichenfolge usw. umzuwandeln. Der Wert der Referenz ändert sich während der Komprimierung (wie Sie gesehen haben), aber jeder vorherige Wert A wird bisher in den Wert B geändert Wenn es um sicheren Code geht, handelt es sich immer noch um eine eindeutige ID.

Wenn sich die beteiligten Objekte unter Ihrer Kontrolle befinden, können Sie eine Zuordnung mit schwachen Referenzen (um die Speicherbereinigung zu verhindern) aus einer Referenz auf eine ID Ihrer Wahl (GUID, Ganzzahl, was auch immer) erstellen . Dies würde jedoch einen gewissen Aufwand und Komplexität verursachen.

Jon Skeet
quelle
1
Ich denke, für Suchvorgänge müssten Sie alle von Ihnen verfolgten Referenzen durchlaufen: WeakReference auf dasselbe Objekt sind nicht gleich, sodass Sie nicht wirklich viel anderes tun können.
Roman Starkov
1
Es könnte nützlich sein, jedem Objekt eine eindeutige 64-Bit-ID zuzuweisen, insbesondere wenn solche IDs nacheinander ausgegeben werden. Ich bin nicht sicher, ob die Nützlichkeit die Kosten rechtfertigen würde, aber so etwas könnte hilfreich sein, wenn man zwei verschiedene unveränderliche Objekte vergleicht und sie gleich findet; Wenn man, wenn möglich, den Verweis auf den neueren mit einem Verweis auf den älteren überschreibt, kann man vermeiden, dass viele redundante Verweise auf identische, aber unterschiedliche Objekte vorhanden sind.
Supercat
1
"Kennung." Ich glaube nicht, dass dieses Wort bedeutet, was Sie denken, dass es bedeutet.
Slipp D. Thompson
5
@ SlippD.Thompson: Nein, es ist immer noch eine 1: 1-Beziehung. Es gibt nur einen einzigen Referenzwert, der sich auf ein bestimmtes Objekt bezieht. Dieser Wert wird möglicherweise mehrmals im Speicher angezeigt (z. B. als Wert mehrerer Variablen), ist jedoch immer noch ein einzelner Wert. Es ist wie eine Hausadresse: Ich kann meine Privatadresse auf viele Zettel auf mehrere schreiben, aber das ist immer noch die Kennung für mein Haus. Zwei nicht identische Referenzwerte müssen sich auf unterschiedliche Objekte beziehen - zumindest in C #.
Jon Skeet
1
@supercat: Ich denke, wir können uns in unserem Verständnis von "gekapselten Identitäten" unterscheiden - aber ich denke, wir helfen wahrscheinlich auch niemandem, weiter zu gehen, als wir es bereits getan haben :) Nur eines der Themen, die wir ausführlich diskutieren sollten, wenn wir treffen uns jemals persönlich ...
Jon Skeet
72

Nur .NET 4 und höher

Gute Neuigkeiten alle zusammen!

Das perfekte Tool für diesen Job ist in .NET 4 erstellt und heißt ConditionalWeakTable<TKey, TValue>. Diese Klasse:

  • beliebige Daten mit einem verwalteten Objektinstanz zu assoziieren viel wie ein Wörterbuch verwendet werden (obwohl es ist kein Wörterbuch)
  • hängt nicht von Speicheradressen ab und ist daher immun gegen die GC, die den Heap verdichtet
  • hält Objekte nicht am Leben, nur weil sie als Schlüssel in die Tabelle eingegeben wurden, sodass sie verwendet werden können, ohne dass jedes Objekt in Ihrem Prozess für immer lebendig bleibt
  • verwendet die Referenzgleichheit, um die Objektidentität zu bestimmen; Beim Verschieben können Klassenautoren dieses Verhalten nicht ändern, sodass es für Objekte aller Art konsistent verwendet werden kann
  • kann im laufenden Betrieb ausgefüllt werden, erfordert also nicht, dass Sie Code in Objektkonstruktoren einfügen
Jon
quelle
5
Nur der Vollständigkeit ConditionalWeakTablehalber : verlässt sich auf RuntimeHelpers.GetHashCodeund object.ReferenceEqualstut sein Innenleben. Das Verhalten ist dasselbe wie beim Erstellen eines IEqualityComparer<T>, das diese beiden Methoden verwendet. Wenn Sie Leistung benötigen, empfehle ich dies, da ConditionalWeakTablealle Vorgänge gesperrt sind, um die Thread-Sicherheit zu gewährleisten.
atlaste
1
@StefandeBruijn: A ConditionalWeakTableenthält einen Verweis auf jeden, Valueder nur so stark ist wie der Verweis auf den entsprechenden Key. Ein Objekt, auf das a ConditionalWeakTabledie einzige noch vorhandene Referenz im gesamten Universum enthält, hört automatisch auf zu existieren, wenn der Schlüssel dies tut.
Supercat
41

Die ObjectIDGenerator- Klasse ausgecheckt ? Dies macht das, was Sie versuchen und was Marc Gravell beschreibt.

Der ObjectIDGenerator verfolgt zuvor identifizierte Objekte. Wenn Sie nach der ID eines Objekts fragen, weiß der ObjectIDGenerator, ob die vorhandene ID zurückgegeben oder eine neue ID generiert und gespeichert werden soll.

Die IDs sind für die Lebensdauer der ObjectIDGenerator-Instanz eindeutig. Im Allgemeinen dauert ein ObjectIDGenerator-Leben so lange wie der Formatierer, der es erstellt hat. Objekt-IDs haben nur innerhalb eines bestimmten serialisierten Streams eine Bedeutung und werden verwendet, um zu verfolgen, welche Objekte im serialisierten Objektdiagramm Verweise auf andere Objekte haben.

Mithilfe einer Hash-Tabelle behält der ObjectIDGenerator bei, welche ID welchem ​​Objekt zugewiesen ist. Die Objektreferenzen, die jedes Objekt eindeutig identifizieren, sind Adressen im zur Laufzeit vom Müll gesammelten Heap. Objektreferenzwerte können sich während der Serialisierung ändern, die Tabelle wird jedoch automatisch aktualisiert, damit die Informationen korrekt sind.

Objekt-IDs sind 64-Bit-Zahlen. Die Zuordnung beginnt bei Eins, daher ist Null niemals eine gültige Objekt-ID. Ein Formatierer kann einen Nullwert auswählen, um eine Objektreferenz darzustellen, deren Wert eine Nullreferenz ist (Nothing in Visual Basic).

sisve
quelle
5
Reflector sagt mir, dass ObjectIDGenerator eine Hashtabelle ist, die auf der Standard-GetHashCode-Implementierung basiert (dh keine Benutzerüberladungen verwendet).
Anton Tykhyy
Wahrscheinlich die beste Lösung, wenn druckbare eindeutige IDs erforderlich sind.
Roman Starkov
ObjectIDGenerator ist auch auf dem Telefon nicht implementiert.
Anthony Wieser
Ich verstehe nicht genau, was ObjectIDGenerator tut, aber es scheint zu funktionieren, selbst wenn RuntimeHelpers.GetHashCode verwendet wird. Ich habe beide getestet und nur RuntimeHelpers.GetHashCode schlägt in meinem Fall fehl.
Daniel Bişar
+1 - Funktioniert ziemlich gut (zumindest auf dem Desktop).
Hot Licks
37

RuntimeHelpers.GetHashCode()kann helfen ( MSDN ).

Anton Gogolev
quelle
2
Das mag helfen, aber mit Kosten - IIRC, wenn das Basisobjekt verwendet wird. GetHashCode () muss einen Synchronisationsblock zuweisen, der nicht kostenlos ist. Gute Idee - +1 von mir.
Jon Skeet
Danke, ich kannte diese Methode nicht. Es wird jedoch auch kein eindeutiger Hash-Code erzeugt (verhält sich genauso wie der Beispielcode in der Frage). Wird jedoch nützlich sein, wenn der Benutzer Hash-Code überschreibt, um die Standardversion aufzurufen.
Martin Konicek
1
Sie können GCHandle verwenden, wenn Sie nicht zu viele davon benötigen (siehe unten).
Anton Tykhyy
42
Ein Buch über .NET eines hoch angesehenen Autors besagt, dass RuntimeHelpers.GetHashCode () einen Code erzeugt, der innerhalb einer AppDomain eindeutig ist, und dass Microsoft die Methode GetUniqueObjectID hätte benennen können. Das ist einfach falsch. Beim Testen stellte ich fest, dass ich normalerweise ein Duplikat erhalten würde, wenn ich 10.000 Instanzen eines Objekts (eine WinForms-Textbox) erstellt hatte und niemals über 30.000 hinausgehen konnte. Code, der sich auf die vermeintliche Einzigartigkeit stützte, verursachte zeitweise Abstürze in einem Produktionssystem, nachdem nicht mehr als 1/10 so viele Objekte erstellt wurden.
Jan Hettich
3
@supercat: Aha - habe gerade einige Beweise aus dem Jahr 2003 gefunden, die aus .NET 1.0 und 1.1 stammten. Sieht so aus, als wollten
Jon Skeet
7

Sie können Ihr eigenes Ding in einer Sekunde entwickeln. Zum Beispiel:

   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++;
        }
    }   

Sie können selbst auswählen, was Sie als eindeutige ID haben möchten, z. B. System.Guid.NewGuid () oder einfach eine Ganzzahl für den schnellsten Zugriff.

Majkinetor
quelle
2
Hilft nicht, wenn Sie dies für DisposeFehler benötigen , da dies jede Art von Entsorgung verhindern würde.
Roman
1
Dies funktioniert nicht ganz, da das Wörterbuch Gleichheit anstelle von Identität verwendet und Objekte reduziert, die dieselben Werte für Objekt zurückgeben. Gleichheiten
Anthony Wieser
1
Dadurch bleibt das Objekt jedoch am Leben.
Martin Lottering
1
@MartinLottering was ist, wenn er ConditionalWeakTable <object, idType> verwendet?
Demetris Leptos
7

Wie wäre es mit dieser Methode:

Setzen Sie ein Feld im ersten Objekt auf einen neuen Wert. Wenn dasselbe Feld im zweiten Objekt denselben Wert hat, ist es wahrscheinlich dieselbe Instanz. Andernfalls beenden Sie als anders.

Setzen Sie nun das Feld im ersten Objekt auf einen anderen neuen Wert. Wenn dasselbe Feld im zweiten Objekt in einen anderen Wert geändert wurde, handelt es sich definitiv um dieselbe Instanz.

Vergessen Sie nicht, das Feld im ersten Objekt beim Beenden auf den ursprünglichen Wert zurückzusetzen.

Probleme?

Dawg
quelle
4

In Visual Studio ist es möglich, eine eindeutige Objektkennung zu erstellen: Klicken Sie im Überwachungsfenster mit der rechten Maustaste auf die Objektvariable und wählen Sie im Kontextmenü die Option Objekt-ID erstellen.

Leider ist dies ein manueller Schritt, und ich glaube nicht, dass auf die Kennung über Code zugegriffen werden kann.

Thomas Bratt
quelle
Welche Versionen von Visual Studio verfügen über diese Funktion? Zum Beispiel die Express-Versionen?
Peter Mortensen
3

Sie müssten eine solche Kennung selbst manuell zuweisen - entweder innerhalb der Instanz oder extern.

Für Datensätze, die sich auf eine Datenbank beziehen, kann der Primärschlüssel hilfreich sein (Sie können jedoch weiterhin Duplikate erhalten). Alternativ können Sie entweder einen verwenden Guidoder Ihren eigenen Zähler behalten, indem Sie ihn zuweisen Interlocked.Increment(und ihn so groß machen, dass er wahrscheinlich nicht überläuft).

Marc Gravell
quelle
2

Ich weiß, dass dies beantwortet wurde, aber es ist zumindest nützlich zu beachten, dass Sie Folgendes verwenden können:

http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx

Was Ihnen nicht direkt eine "eindeutige ID" gibt, sondern in Kombination mit WeakReferences (und einem Hashset?) Eine ziemlich einfache Möglichkeit bietet, verschiedene Instanzen zu verfolgen.

Andrew Theken
quelle
1

Die Informationen, die ich hier gebe, sind nicht neu. Der Vollständigkeit halber habe ich sie nur hinzugefügt.

Die Idee dieses Codes ist ganz einfach:

  • Objekte benötigen eine eindeutige ID, die standardmäßig nicht vorhanden ist. Stattdessen müssen wir uns auf das nächstbeste verlassen RuntimeHelpers.GetHashCode, nämlich eine Art eindeutige ID zu erhalten
  • Um die Eindeutigkeit zu überprüfen, müssen wir diese verwenden object.ReferenceEquals
  • Wir möchten jedoch immer noch eine eindeutige ID haben, daher habe ich eine hinzugefügt GUID, die per Definition eindeutig ist.
  • Weil ich nicht gerne alles sperre, wenn ich nicht muss, benutze ich nicht ConditionalWeakTable.

In Kombination erhalten Sie den folgenden Code:

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;
    }
}

Um es zu verwenden, erstellen Sie eine Instanz von UniqueIdMapperund verwenden Sie die zurückgegebenen GUIDs für die Objekte.


Nachtrag

Hier ist also noch ein bisschen mehr los. Lass mich ein bisschen darüber schreiben ConditionalWeakTable.

ConditionalWeakTablemacht ein paar Dinge. Das Wichtigste ist, dass der Garbage Collector keine Rolle spielt, dh, die Objekte, auf die Sie in dieser Tabelle verweisen, werden unabhängig davon erfasst. Wenn Sie ein Objekt nachschlagen, funktioniert es im Wesentlichen genauso wie das obige Wörterbuch.

Neugierig nein? Wenn ein Objekt vom GC erfasst wird, prüft es schließlich, ob Verweise auf das Objekt vorhanden sind, und erfasst diese, falls vorhanden. Wenn es also ein Objekt aus dem gibt ConditionalWeakTable, warum wird das referenzierte Objekt dann gesammelt?

ConditionalWeakTableverwendet einen kleinen Trick, den auch einige andere .NET-Strukturen verwenden: Anstatt einen Verweis auf das Objekt zu speichern, wird tatsächlich ein IntPtr gespeichert. Da dies keine echte Referenz ist, kann das Objekt gesammelt werden.

An dieser Stelle sind also zwei Probleme zu lösen. Erstens können Objekte auf dem Heap verschoben werden. Was werden wir also als IntPtr verwenden? Und zweitens, woher wissen wir, dass Objekte eine aktive Referenz haben?

  • Das Objekt kann auf dem Heap fixiert und sein realer Zeiger gespeichert werden. Wenn der GC das Objekt zum Entfernen trifft, löst er es und sammelt es ein. Dies würde jedoch bedeuten, dass wir eine angeheftete Ressource erhalten. Dies ist keine gute Idee, wenn Sie viele Objekte haben (aufgrund von Problemen mit der Speicherfragmentierung). So funktioniert es wahrscheinlich nicht.
  • Wenn der GC ein Objekt verschiebt, ruft er zurück, wodurch die Referenzen aktualisiert werden können. So könnte es umgesetzt werden, gemessen an den externen Anrufen DependentHandle- aber ich glaube, es ist etwas ausgefeilter.
  • Es wird nicht der Zeiger auf das Objekt selbst gespeichert, sondern ein Zeiger in der Liste aller Objekte aus dem GC. Der IntPtr ist entweder ein Index oder ein Zeiger in dieser Liste. Die Liste ändert sich nur, wenn ein Objekt Generationen ändert. Zu diesem Zeitpunkt kann ein einfacher Rückruf die Zeiger aktualisieren. Wenn Sie sich daran erinnern, wie Mark & ​​Sweep funktioniert, ist dies sinnvoller. Es gibt kein Fixieren und das Entfernen ist wie zuvor. Ich glaube, so funktioniert es DependentHandle.

Diese letzte Lösung erfordert, dass die Laufzeit die Listen-Buckets erst wiederverwendet, wenn sie explizit freigegeben wurden, und dass alle Objekte durch einen Aufruf der Laufzeit abgerufen werden müssen.

Wenn wir davon ausgehen, dass sie diese Lösung verwenden, können wir auch das zweite Problem angehen. Der Mark & ​​Sweep-Algorithmus verfolgt, welche Objekte gesammelt wurden. Sobald es gesammelt wurde, wissen wir an dieser Stelle. Sobald das Objekt prüft, ob das Objekt vorhanden ist, ruft es 'Free' auf, wodurch der Zeiger und der Listeneintrag entfernt werden. Das Objekt ist wirklich weg.

Eine wichtige Sache, die an dieser Stelle zu beachten ist, ist, dass Dinge schrecklich schief gehen, wenn sie ConditionalWeakTablein mehreren Threads aktualisiert werden und wenn sie nicht threadsicher sind. Das Ergebnis wäre ein Speicherverlust. Aus diesem Grund führen alle eingehenden Anrufe ConditionalWeakTableeine einfache Sperre durch, die sicherstellt, dass dies nicht geschieht.

Eine andere Sache zu beachten ist, dass das Bereinigen von Einträgen von Zeit zu Zeit erfolgen muss. Während die tatsächlichen Objekte vom GC bereinigt werden, sind dies die Einträge nicht. Deshalb ConditionalWeakTablewächst nur die Größe. Sobald es ein bestimmtes Limit erreicht (bestimmt durch die Kollisionswahrscheinlichkeit im Hash), löst es ein aus Resize, das prüft, ob Objekte bereinigt werden müssen - wenn dies der Fall freeist , wird es im GC-Prozess aufgerufen und das IntPtrHandle entfernt.

Ich glaube, dies ist auch der Grund, warum DependentHandlenicht direkt belichtet wird - Sie möchten sich nicht mit Dingen anlegen und dadurch ein Speicherleck bekommen. Das nächstbeste dafür ist a WeakReference(das auch IntPtrein Objekt anstelle eines Objekts speichert ) - enthält aber leider nicht den Aspekt 'Abhängigkeit'.

Was bleibt, ist, dass Sie mit der Mechanik herumspielen, damit Sie die Abhängigkeit in Aktion sehen können. Starten Sie es mehrmals und sehen Sie sich die Ergebnisse an:

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
    }
atlaste
quelle
1
A ConditionalWeakTablekönnte besser sein, da es die Darstellungen für Objekte nur beibehalten würde, wenn Verweise auf sie vorhanden wären. Außerdem würde ich vorschlagen, dass eine Int64möglicherweise besser als eine GUID ist, da Objekte einen dauerhaften Rang erhalten könnten . Solche Dinge können in Sperrszenarien nützlich sein (z. B. kann man einen Deadlock vermeiden, wenn ein Code, der mehrere Sperren erhalten muss, dies in einer definierten Reihenfolge tut, aber damit dies funktioniert, muss es eine geben eine definierte Reihenfolge geben).
Supercat
@supercat Sicher über die longs; es hängt von Ihrem Szenario ab - in f.ex. Verteilte Systeme Es ist manchmal nützlicher, mit GUIDs zu arbeiten . Was ConditionalWeakTable: du hast recht; DependentHandleprüft auf Lebendigkeit (HINWEIS: nur wenn die Größe des Dings geändert wird!), was hier nützlich sein kann. Wenn Sie jedoch Leistung benötigen, kann das Sperren dort zu einem Problem werden. In diesem Fall könnte es interessant sein, dies zu verwenden. Um ehrlich zu sein, mag ich die Implementierung von persönlich nicht ConditionalWeakTable, was wahrscheinlich zu meiner Tendenz führt, ein einfaches Dictionary- sogar zu verwenden obwohl du richtig bist.
Atlaste
Ich war lange neugierig, wie das ConditionalWeakTabletatsächlich funktioniert. Die Tatsache, dass nur Elemente hinzugefügt werden können, lässt mich denken, dass es so konzipiert ist, dass der Aufwand für die Parallelität minimiert wird, aber ich habe keine Ahnung, wie es intern funktioniert. Ich finde es merkwürdig, dass es keinen einfachen DependentHandleWrapper gibt, der keine Tabelle verwendet, da es definitiv Zeiten gibt, in denen es wichtig ist, sicherzustellen, dass ein Objekt für die Lebensdauer eines anderen am Leben bleibt, aber das letztere Objekt keinen Platz für eine Referenz hat zum ersten.
Supercat
@supercat Ich werde einen Nachtrag darüber veröffentlichen, wie ich denke, dass es funktioniert.
Atlaste
Das ConditionalWeakTableerlaubt nicht, dass Einträge, die in der Tabelle gespeichert wurden, geändert werden. Als solches würde ich denken, dass es sicher unter Verwendung von Speicherbarrieren, aber nicht von Sperren implementiert werden könnte. Die einzige problematische Situation wäre, wenn zwei Threads gleichzeitig versuchen würden, denselben Schlüssel hinzuzufügen. Dies könnte behoben werden, indem die "add" -Methode nach dem Hinzufügen eines Elements eine Speicherbarriere durchführt und dann scannt, um sicherzustellen, dass genau ein Element diesen Schlüssel hat. Wenn mehrere Elemente denselben Schlüssel haben, kann eines als "erstes" identifiziert werden, sodass die anderen Elemente entfernt werden können.
Supercat
0

Wenn Sie ein Modul in Ihrem eigenen Code für eine bestimmte Verwendung schreiben, hat die Methode von Majkinetor möglicherweise funktioniert. Es gibt jedoch einige Probleme.

Erstens garantiert das offizielle Dokument NICHT , dass das Dokument GetHashCode()eine eindeutige Kennung zurückgibt (siehe Object.GetHashCode-Methode () ):

Sie sollten nicht davon ausgehen, dass gleiche Hash-Codes Objektgleichheit implizieren.

Zweitens wird davon ausgegangen, dass Sie nur eine sehr kleine Anzahl von Objekten haben, sodass GetHashCode()diese Methode in den meisten Fällen von einigen Typen überschrieben werden kann.
Zum Beispiel verwenden Sie eine Klasse C und diese überschreibt, GetHashCode()um immer 0 zurückzugeben. Dann erhält jedes Objekt von C den gleichen Hash-Code. Leider Dictionary, HashTableund einige andere assoziative Container wird diese Methode machen verwenden:

Ein Hash-Code ist ein numerischer Wert, mit dem ein Objekt in eine Hash-basierte Sammlung eingefügt und identifiziert wird, z. B. die Dictionary <TKey, TValue> -Klasse, die Hashtable-Klasse oder ein von der DictionaryBase-Klasse abgeleiteter Typ. Die GetHashCode-Methode stellt diesen Hash-Code für Algorithmen bereit, die eine schnelle Überprüfung der Objektgleichheit erfordern.

Dieser Ansatz weist also große Einschränkungen auf.

Und noch mehr , was ist, wenn Sie eine Allzweckbibliothek erstellen möchten? Sie können nicht nur den Quellcode der verwendeten Klassen nicht ändern, sondern ihr Verhalten ist auch nicht vorhersehbar.

Ich schätze das Jon und Simon ihre Antworten veröffentlicht haben, und ich werde unten ein Codebeispiel und einen Vorschlag zur Leistung veröffentlichen.

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();
    }
}

In meinem Test wird der ObjectIDGeneratoreine Ausnahme auslösen, um sich darüber zu beschweren, dass beim Erstellen von 10.000.000 Objekten (10x als im obigen Code) in der forSchleife zu viele Objekte vorhanden sind .

Das Benchmark-Ergebnis ist außerdem, dass die ConditionalWeakTableImplementierung 1,8-mal schneller ist als die ObjectIDGeneratorImplementierung.

Mr. Ree
quelle