Czy istnieje sposób na uzyskanie unikalnego identyfikatora instancji?
GetHashCode()
jest taki sam dla dwóch odniesień wskazujących na to samo wystąpienie. Jednak dwie różne instancje mogą (dość łatwo) uzyskać ten sam kod skrótu:
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);
}
Piszę dodatek do debugowania i potrzebuję jakiegoś identyfikatora dla odniesienia, który jest unikalny podczas działania programu.
Udało mi się już uzyskać wewnętrzny ADRES instancji, który jest unikalny do momentu, gdy garbage collector (GC) kompaktuje stertę (= przenosi obiekty = zmienia adresy).
Pytanie o przepełnienie stosu Domyślna implementacja Object.GetHashCode () może być powiązana.
Obiekty nie są pod moją kontrolą, ponieważ uzyskuję dostęp do obiektów w debugowanym programie za pomocą interfejsu API debugera. Gdybym miał kontrolę nad obiektami, dodanie własnych unikalnych identyfikatorów byłoby trywialne.
Chciałem mieć unikalny identyfikator do budowania hashtable ID -> object, aby móc wyszukiwać już widziane obiekty. Na razie rozwiązałem to tak:
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
}
źródło
Tylko .NET 4 i nowsze
Dobre wieści wszyscy!
Idealne narzędzie do tego zadania jest wbudowane w .NET 4 i nazywa się
ConditionalWeakTable<TKey, TValue>
. Ta klasa:źródło
ConditionalWeakTable
polega na wewnętrznych funkcjachRuntimeHelpers.GetHashCode
iobject.ReferenceEquals
wykonuje je. Zachowanie jest takie samo, jak przy tworzeniu obiektu,IEqualityComparer<T>
który korzysta z tych dwóch metod. Jeśli potrzebujesz wydajności, sugeruję to zrobić, ponieważConditionalWeakTable
ma blokadę wokół wszystkich swoich operacji, aby zapewnić bezpieczeństwo wątków.ConditionalWeakTable
zawiera odniesienie do każdego z nich,Value
które jest tak silne, jak odniesienie utrzymywane w innym miejscu do odpowiedniegoKey
. Obiekt, do którego aConditionalWeakTable
zawiera jedyne istniejące odniesienie w dowolnym miejscu we wszechświecie, automatycznie przestanie istnieć, gdy klucz się pojawi.Wyrejestrowany ObjectIDGenerator klasie? Robi to, co próbujesz zrobić i co opisuje Marc Gravell.
źródło
RuntimeHelpers.GetHashCode()
może pomóc ( MSDN ).źródło
Możesz rozwinąć swoją własną rzecz w ciągu sekundy. Na przykład:
Możesz samodzielnie wybrać, co chcesz mieć jako unikalny identyfikator, na przykład System.Guid.NewGuid () lub po prostu liczbę całkowitą, aby uzyskać najszybszy dostęp.
źródło
Dispose
błędów, ponieważ uniemożliwiłoby to jakąkolwiek utylizację.A co z tą metodą:
Ustaw pole w pierwszym obiekcie na nową wartość. Jeśli to samo pole w drugim obiekcie ma tę samą wartość, prawdopodobnie jest to ta sama instancja. W przeciwnym razie zakończ jak inaczej.
Teraz ustaw pole w pierwszym obiekcie na inną nową wartość. Jeśli to samo pole w drugim obiekcie zmieniło się na inną wartość, jest to zdecydowanie ta sama instancja.
Nie zapomnij przywrócić pierwotnej wartości pola w pierwszym obiekcie przy wyjściu.
Problemy?
źródło
Możliwe jest utworzenie unikatowego identyfikatora obiektu w programie Visual Studio: w oknie obserwacyjnym kliknij prawym przyciskiem myszy zmienną obiektu i wybierz Utwórz identyfikator obiektu z menu kontekstowego.
Niestety jest to krok ręczny i nie sądzę, aby można było uzyskać dostęp do identyfikatora za pomocą kodu.
źródło
Musiałbyś sam przypisać taki identyfikator, ręcznie - wewnątrz instancji lub zewnętrznie.
W przypadku rekordów związanych z bazą danych klucz podstawowy może być przydatny (ale nadal można uzyskać duplikaty). Alternatywnie, użyj
Guid
lub zachowaj własny licznik, alokując za pomocąInterlocked.Increment
(i uczyń go na tyle dużym, aby nie przepełniał).źródło
Wiem, że udzielono odpowiedzi, ale przynajmniej warto zauważyć, że możesz użyć:
http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx
Który nie da Ci bezpośrednio „unikalnego identyfikatora”, ale w połączeniu z WeakReferences (i hashsetem?) Może dać ci całkiem łatwy sposób śledzenia różnych instancji.
źródło
Informacje, które tu podaję, nie są nowe, dodałem je tylko dla kompletności.
Idea tego kodu jest dość prosta:
RuntimeHelpers.GetHashCode
na uzyskaniu swego rodzaju unikalnego identyfikatoraobject.ReferenceEquals
GUID
, który z definicji jest unikalny.ConditionalWeakTable
.W połączeniu daje to następujący kod:
Aby z niego skorzystać, utwórz instancję
UniqueIdMapper
i użyj identyfikatorów GUID, które zwraca dla obiektów.Uzupełnienie
Tak więc dzieje się tu trochę więcej; napiszę trochę o tym
ConditionalWeakTable
.ConditionalWeakTable
robi kilka rzeczy. Najważniejsze jest to, że nie obchodzi go garbage collector, to znaczy: obiekty, do których odwołujesz się w tej tabeli, zostaną zebrane niezależnie. Jeśli szukasz obiektu, działa on w zasadzie tak samo, jak powyższy słownik.Ciekawy nie? W końcu, gdy obiekt jest zbierany przez GC, sprawdza, czy istnieją odwołania do obiektu, a jeśli tak, zbiera je. Więc jeśli istnieje obiekt z
ConditionalWeakTable
, dlaczego w takim przypadku będzie zbierany obiekt , do którego się odwołuje?ConditionalWeakTable
używa małej sztuczki, której również używają inne struktury .NET: zamiast przechowywać odniesienie do obiektu, w rzeczywistości przechowuje IntPtr. Ponieważ to nie jest prawdziwe odniesienie, obiekt można zebrać.W tym momencie należy rozwiązać dwa problemy. Po pierwsze, obiekty można przenosić na stercie, więc czego użyjemy jako IntPtr? Po drugie, skąd wiemy, że obiekty mają aktywne odniesienie?
DependentHandle
- ale uważam, że jest nieco bardziej wyrafinowany.DependentHandle
.To ostatnie rozwiązanie wymaga, aby środowisko wykonawcze nie użyło ponownie zasobników listy, dopóki nie zostaną one jawnie zwolnione, a także wymaga, aby wszystkie obiekty zostały pobrane przez wywołanie środowiska wykonawczego.
Jeśli założymy, że korzystają z tego rozwiązania, możemy również rozwiązać drugi problem. Algorytm Mark & Sweep śledzi, które obiekty zostały zebrane; jak tylko zostanie zebrany, wiemy w tym miejscu. Gdy obiekt sprawdzi, czy obiekt tam jest, wywołuje „Free”, co usuwa wskaźnik i wpis na liście. Obiekt naprawdę zniknął.
W tym miejscu należy zauważyć, że rzeczy idą strasznie źle, jeśli
ConditionalWeakTable
są aktualizowane w wielu wątkach i nie są bezpieczne dla wątków. Rezultatem byłby wyciek pamięci. Dlatego wszystkie połączeniaConditionalWeakTable
wykonują prostą „blokadę”, która zapewnia, że tak się nie stanie.Inną rzeczą, na którą należy zwrócić uwagę, jest to, że czyszczenie wpisów musi odbywać się od czasu do czasu. Chociaż rzeczywiste obiekty zostaną wyczyszczone przez GC, wpisy nie. Dlatego
ConditionalWeakTable
tylko rośnie. Gdy osiągnie pewien limit (określony przez szansę na kolizję w hashu), wyzwala aResize
, który sprawdza, czy obiekty muszą zostać wyczyszczone - jeśli tak,free
jest wywoływane w procesie GC, usuwającIntPtr
uchwyt.Uważam, że jest to również powód, dla którego
DependentHandle
nie jest on ujawniany bezpośrednio - nie chcesz zepsuć rzeczy i w rezultacie uzyskać wyciek pamięci. Następną najlepszą rzeczą do tego jest aWeakReference
(który również przechowujeIntPtr
zamiast obiektu) - ale niestety nie obejmuje aspektu „zależności”.Pozostaje ci bawić się mechaniką, abyś mógł zobaczyć zależność w akcji. Pamiętaj, aby uruchomić go wiele razy i obserwować wyniki:
źródło
ConditionalWeakTable
może być lepsze, ponieważ utrzymywałoby reprezentacje obiektów tylko wtedy, gdy istnieją do nich odniesienia. Sugerowałbym również, żeInt64
może być lepszy niż identyfikator GUID, ponieważ pozwoliłby na nadanie obiektom trwałej rangi . Takie rzeczy mogą być przydatne w scenariuszach blokowania (np. Można uniknąć zakleszczenia, jeśli jeden cały kod, który będzie musiał uzyskać wiele zamków, robi to w określonej kolejności, ale aby to zadziałało, musi być określona kolejność).long
s; to zależy od twojego scenariusza - np. systemy rozproszone czasami bardziej przydatna jest praca zGUID
s. A co doConditionalWeakTable
: masz rację;DependentHandle
sprawdza żywotność (UWAGA: tylko wtedy, gdy rzecz zmienia rozmiar!), co może być tutaj przydatne. Mimo to, jeśli potrzebujesz wydajności, blokowanie może stać się tam problemem, więc w takim przypadku może być interesujące skorzystanie z tego ... szczerze mówiąc osobiście nie lubię implementacjiConditionalWeakTable
, co prawdopodobnie prowadzi do mojej stronniczości korzystania z prostegoDictionary
- równego chociaż masz rację.ConditionalWeakTable
właściwie działa. Fakt, że pozwala tylko na dodawanie elementów, sprawia, że myślę, że został zaprojektowany, aby zminimalizować narzut związany ze współbieżnością, ale nie mam pojęcia, jak to działa wewnętrznie. Ciekawe, że nie ma prostegoDependentHandle
opakowania, które nie używa stołu, ponieważ z pewnością są chwile, kiedy ważne jest, aby jeden obiekt był żywy przez całe życie innego, ale ten drugi obiekt nie ma miejsca na odniesienie do pierwszego.ConditionalWeakTable
wpisy nie pozwala, które zostały zapisane w tabeli, aby być modyfikowane. W związku z tym myślę, że można go bezpiecznie zaimplementować za pomocą barier pamięci, ale nie blokad. Jedyną problematyczną sytuacją byłoby, gdyby dwa wątki próbowały dodać ten sam klucz jednocześnie; Problem ten można rozwiązać, wykonując za pomocą metody „add” barierę pamięci po dodaniu elementu, a następnie skanując, aby upewnić się, że klucz ma dokładnie jeden element. Jeśli wiele elementów ma ten sam klucz, jeden z nich będzie identyfikowany jako „pierwszy”, więc możliwe będzie wyeliminowanie pozostałych.Jeśli piszesz moduł we własnym kodzie do określonego zastosowania, metoda majkinetora MOŻE zadziałać. Ale są pewne problemy.
Po pierwsze , oficjalny dokument NIE gwarantuje, że
GetHashCode()
zwraca unikalny identyfikator (patrz Metoda Object.GetHashCode () ):Po drugie , załóżmy, że masz bardzo małą liczbę obiektów, więc
GetHashCode()
w większości przypadków będzie działać, ta metoda może zostać zastąpiona przez niektóre typy.Na przykład, używasz pewnej klasy C i nadpisuje
GetHashCode()
ona zawsze zwracanie 0. Wtedy każdy obiekt C otrzyma ten sam kod skrótu. NiestetyDictionary
,HashTable
i kilka innych asocjacyjne pojemniki będą wykorzystywać tę metodę:Tak więc to podejście ma wielkie ograniczenia.
A co więcej , jeśli chcesz zbudować bibliotekę ogólnego przeznaczenia? Nie tylko nie jesteś w stanie modyfikować kodu źródłowego użytych klas, ale także ich zachowanie jest nieprzewidywalne.
Doceniam, że Jon i Simon opublikowali swoje odpowiedzi, a poniżej zamieszczę przykładowy kod i sugestię dotyczącą wydajności.
W moim teście
ObjectIDGenerator
zgłosi wyjątek, aby narzekać, że podczas tworzenia 10000000 obiektów jest zbyt wiele obiektów (10x niż w powyższym kodzie) wfor
pętli.Wynik testu porównawczego jest taki, że
ConditionalWeakTable
implementacja jest 1,8x szybsza niżObjectIDGenerator
implementacja.źródło