. NET unikalny identyfikator obiektu

Czy istnieje sposób na uzyskanie unikalnego identyfikatora instancji?

GetHashCode() jest takie samo dla dwóch odwołań wskazujących na tę samą instancję. Jednak dwie różne instancje mogą (dość łatwo) uzyskać ten sam kod hashowy:

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 muszę uzyskać jakiś identyfikator odniesienia, który jest unikalny podczas uruchamiania programu.

Udało mi się już uzyskać wewnętrzny adres instancji, który jest unikalny aż do garbage collector (GC) zagęszcza stertę (=przesuwa obiekty = zmienia adresy).

Pytanie o przepełnienie stosu Domyślna implementacja dla obiektu.GetHashCode() to może być powiązane.

Obiekty nie są pod moją kontrolą, ponieważ uzyskuję dostęp do obiektów w programie debugowanym za pomocą interfejsu API debuggera. Gdybym miał kontrolę nad obiektami, dodanie własnych unikalnych identyfikatorów byłoby banalne.

Chciałem, aby unikalny identyfikator do budowania hashtable ID - > object, był w stanie wyszukać 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
}
Author: Community, 2009-04-15

11 answers

Odniesienie jest unikalnym identyfikatorem obiektu. Nie znam żadnego sposobu na przekształcenie tego w coś takiego jak łańcuch itp. Wartość referencji zmieni się podczas zagęszczania (jak widać), ale każda poprzednia wartość A zostanie zmieniona na wartość B, więc jeśli chodzi o bezpieczny kod, nadal jest to unikalny identyfikator.

Jeśli obiekty są pod twoją kontrolą, możesz utworzyć mapowanie za pomocą słabych odniesień (aby uniknąć zapobiegania zbieraniu śmieci) od referencji do wybranego przez Ciebie ID (GUID, integer, cokolwiek). To dodałoby jednak pewną ilość kosztów ogólnych i złożoności.

 36
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 09:44:40

. NET 4 i Później tylko

Dobre wieści!

Idealne narzędzie do tego zadania jest wbudowane w. Net 4 i nazywa się ConditionalWeakTable<TKey, TValue>. Klasa Ta:

    Może być używany do kojarzenia dowolnych danych z zarządzanymi instancjami obiektów, podobnie jak słownik (chociaż jest nie jest słownikiem) Nie zależy od adresów pamięci, więc jest odporny na GC zagęszczające stertę
  • nie utrzymuje obiektów przy życiu tylko dlatego, że zostały wprowadzone jako klucze do tabeli, więc może być używany bez sprawiania, że każdy obiekt w procesie będzie żył na zawsze
  • Moveover, autorzy klas nie mogą modyfikować tego zachowania, więc można go używać konsekwentnie na obiektach dowolnego typu
  • może być wypełniany w locie, więc nie wymaga wprowadzania kodu wewnątrz konstruktorów obiektów
 56
Author: Jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-26 04:59:13

Sprawdziłeś klasę ObjectIDGenerator ? Robi to, co próbujesz zrobić i co opisuje Marc Gravell.

ObjectIDGenerator śledzi wcześniej zidentyfikowane obiekty. Gdy zapytasz o ID obiektu, ObjectIDGenerator wie, czy zwrócić istniejący identyfikator, czy wygenerować i zapamiętać nowy identyfikator.

Identyfikatory są unikalne przez cały okres istnienia instancji ObjectIDGenerator. Ogólnie rzecz biorąc, życie obiektu trwa tak długo, jak długo Formatter, który go stworzył. Identyfikatory obiektów mają znaczenie tylko w danym serializowanym strumieniu i są używane do śledzenia, które obiekty mają odniesienia do innych w serializowanym wykresie obiektów.

Używając tabeli hash, Obiekttidgenerator zachowuje identyfikator przypisany do danego obiektu. Odniesienia do obiektów, które jednoznacznie identyfikują każdy obiekt, są adresami w stercie śmieci pobranych w trybie runtime. Wartości odniesienia obiektów mogą się zmieniać podczas serializacji, ale tabela jest aktualizowana automatycznie, więc informacje są poprawne.

Identyfikatory obiektów są liczbami 64-bitowymi. Alokacja zaczyna się od jedynki, więc zero nigdy nie jest poprawnym ID obiektu. Formatator może wybrać wartość zerową, aby reprezentować odniesienie do obiektu, którego wartość jest odniesieniem null (nic w Visual Basic).

 36
Author: sisve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 10:58:53

RuntimeHelpers.GetHashCode() może pomóc ( MSDN ).

 31
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-19 13:50:24

Możesz rozwinąć swoje własne rzeczy w sekundę. Na przykład:

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

Możesz wybrać to, co chcesz mieć jako unikalne ID na własną rękę, na przykład System.Guid.NewGuid() lub po prostu integer dla najszybszego dostępu.

 7
Author: majkinetor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 11:31:25

A może ta metoda:

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 wyjdź jako inny.

Ustaw teraz pole w pierwszym obiekcie na inną nową wartość. Jeśli to samo pole w drugim obiekcie zmieniło się na inną wartość, na pewno jest to ta sama instancja.

Nie zapomnij ustawić pola w pierwszym obiekcie z powrotem do jego pierwotnej wartości na wyjście.

Problemy?

 6
Author: Dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-22 22:03:41

W Visual Studio można utworzyć unikalny identyfikator obiektu: w oknie obserwuj kliknij zmienną obiekt prawym przyciskiem myszy i wybierz Utwórz ID obiektu z menu kontekstowego.

Niestety, jest to krok ręczny i nie wierzę, że identyfikator można uzyskać za pomocą kodu.

 4
Author: Thomas Bratt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-10 01:38:09

Musiałbyś przypisać taki identyfikator samodzielnie, ręcznie-wewnątrz instancji lub na zewnątrz.

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 swój własny licznik, przydzielając za pomocą Interlocked.Increment (i uczyń go na tyle dużym, że prawdopodobnie nie przepełni się).

 3
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 09:44:52

Wiem, że na to odpowiedź, ale warto przynajmniej zauważyć, że można użyć:

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

Który nie da ci bezpośrednio "unikalnego id", ale w połączeniu ze słabym identyfikatorem (i hashset?) może dać ci dość łatwy sposób śledzenia różnych instancji.

 2
Author: Andrew Theken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-20 18:32:47

Informacje, które tu podaję, nie są nowe, dodałem to tylko dla kompletności.

Idea tego kodu jest dość prosta:]}
  • Obiekty potrzebują unikalnego identyfikatora, którego domyślnie nie ma. Zamiast tego musimy polegać na następnej najlepszej rzeczy, jaką jest RuntimeHelpers.GetHashCode, aby uzyskać unikalny identyfikator
  • aby sprawdzić wyjątkowość, oznacza to, że musimy użyć object.ReferenceEquals
  • jednak nadal chcielibyśmy mieć unikalny identyfikator, więc dodałem GUID, który jest z definicji wyjątkowe.
  • ponieważ nie lubię zamykać wszystkiego, jeśli nie muszę, nie używam ConditionalWeakTable.

Razem, to da ci następujący kod:

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

Aby go użyć, Utwórz instancję UniqueIdMapper i użyj zwracanego identyfikatora GUID dla obiektów.


Dodatek

Więc, tu się dzieje trochę więcej; pozwól, że napiszę trochę o ConditionalWeakTable.

ConditionalWeakTable robi kilka rzeczy. Najważniejsze jest to, że nie dbaj o garbage collector, to znaczy: obiekty, do których odwołujesz się w tej tabeli, będą zbierane niezależnie od tego. Jeśli wyszukujesz obiekt, działa on w zasadzie tak samo jak powyższy słownik.

Ciekawe nie? Wszakże gdy GC gromadzi jakiś obiekt, sprawdza czy istnieją odniesienia do niego, a jeśli są, to je zbiera. Jeśli więc istnieje obiekt z ConditionalWeakTable, to dlaczego obiekt odwołujący się zostanie wtedy zebrany?

ConditionalWeakTable używa małej sztuczki, która niektóre inne struktury. NET również używają: zamiast przechowywać odniesienie do obiektu, przechowuje IntPtr. Ponieważ nie jest to prawdziwe odniesienie, obiekt można zebrać.

W tym momencie są dwa problemy do rozwiązania. Po pierwsze, obiekty mogą być przenoszone na stertę, więc czego użyjemy jako IntPtr? A po drugie, skąd wiemy, że obiekty mają aktywne odniesienie?

  • obiekt może być przypięty do sterty, a jego rzeczywisty wskaźnik może być przechowywany. Gdy GC uderzy w obiekt do usunięcia, rozpakowuje go i zbiera. Oznaczałoby to jednak, że otrzymujemy przypięty zasób, co nie jest dobrym pomysłem, jeśli masz dużo obiektów (ze względu na problemy z fragmentacją pamięci). Prawdopodobnie tak to nie działa.
  • gdy GC przesuwa obiekt, oddzwania, co może następnie aktualizować referencje. Może tak to jest zaimplementowane sądząc po wywołaniach zewnętrznych w DependentHandle - ale uważam, że jest to nieco bardziej wyrafinowane.
  • Nie wskaźnik do samego obiektu, ale wskaźnik na liście wszystkich obiektów z GC jest przechowywany. IntPtr jest indeksem lub Wskaźnikiem na tej liście. Lista zmienia się tylko wtedy, gdy obiekt zmienia generacje, w którym to momencie proste wywołanie zwrotne może zaktualizować wskaźniki. Jeśli pamiętasz, jak działa Mark & Sweep, ma to większy sens. Nie ma przypięcia, a usunięcie jest takie, jakie było wcześniej. Wierzę, że tak to działa w DependentHandle.

To ostatnie rozwiązanie wymaga, aby runtime nie używało ponownie łyżek listy dopóki nie zostaną jawnie zwolnione, a także wymaga, aby wszystkie obiekty były pobierane przez wywołanie do środowiska uruchomieniowego.

Jeśli założymy, że używają tego rozwiązania, możemy również rozwiązać drugi problem. Algorytm Mark & Sweep śledzi, które obiekty zostały zebrane; jak tylko zostały zebrane, wiemy w tym momencie. Gdy obiekt sprawdzi, czy obiekt Tam jest, wywoła "Free", co usuwa wskaźnik i wpis listy. Obiekt naprawdę zniknął.

Jeden ważny w tym momencie należy zauważyć, że rzeczy idą strasznie źle, jeśli ConditionalWeakTable jest aktualizowany w wielu wątkach i jeśli nie jest bezpieczny wątek. Rezultatem byłby wyciek pamięci. Dlatego wszystkie wywołania ConditionalWeakTable wykonują prostą 'blokadę', która gwarantuje, że tak się nie stanie.

Kolejną rzeczą do zauważenia jest to, że sprzątanie wpisów musi się zdarzyć raz na jakiś czas. Podczas gdy rzeczywiste obiekty zostaną oczyszczone przez GC, wpisy nie są. To dlatego ConditionalWeakTable rośnie tylko w rozmiarze. Gdy osiągnie określony limit (określony poprzez przypadek kolizji w hash), uruchamia Resize, który sprawdza, czy obiekty muszą być oczyszczone - jeśli tak się stanie, free jest wywoływany w procesie GC, usuwając uchwyt IntPtr.

Uważam, że to również dlatego DependentHandle nie jest bezpośrednio narażony-nie chcesz mieszać z rzeczami i uzyskać wyciek pamięci w wyniku. Następną najlepszą rzeczą do tego jest WeakReference (która również przechowuje IntPtr zamiast obiektu) - ale niestety nie zawiera aspektu 'zależności'.

Co pozostaje jest do zabawy z mechaniką, tak, że można zobaczyć zależność w akcji. Pamiętaj, aby uruchomić go wiele razy i oglądać wyniki:

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
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-09 10:14:59

Jeśli piszesz moduł we własnym kodzie do określonego zastosowania, metoda majkinetora mogło zadziałać. Ale są pewne problemy.

Po pierwsze , oficjalny dokument nie gwarantuje , że zwróci unikalny identyfikator (patrz obiekt.Metoda GetHashCode ()):

Nie należy zakładać, że równe kody hash oznaczają równość obiektów.

Druga , Załóżmy, że mieć bardzo małą ilość obiektów, dzięki czemu GetHashCode() będzie działać w większości przypadków, metoda ta może być nadpisana przez niektóre typy.
Na przykład, używasz jakiejś klasy C i to nadpisuje GetHashCode(), aby zawsze zwracać 0. Wtedy każdy obiekt C otrzyma ten sam kod hashowy. Niestety, Dictionary, HashTable i niektóre inne kontenery asocjacyjne będą używać tej metody:

Kod hash jest wartością liczbową, która jest używana do wstawiania i identyfikowania obiektu w kolekcji opartej na hashach, takiej jak Dictionary class, the Hashtable class, or a type derived from the DictionaryBase class. Metoda GetHashCode dostarcza ten kod skrótu dla algorytmów, które wymagają szybkiej kontroli równości obiektów.

Takie podejście ma więc wielkie ograniczenia.

I jeszcze bardziej, co zrobić, jeśli chcesz zbudować bibliotekę ogólnego przeznaczenia? Nie tylko nie możesz modyfikować kodu źródłowego używanych klas, ale ich zachowanie jest również nieprzewidywalne.

I doceniam to, że Jon i Simon opublikowali swoje odpowiedzi, a Ja podam przykład kodu i sugestię dotyczącą wydajności poniżej.

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

W moim teście, ObjectIDGenerator rzuci wyjątek, aby narzekać, że jest zbyt wiele obiektów podczas tworzenia 10,000,000 obiektów (10x niż w powyższym kodzie) w pętli for.

Również wynik benchmarka jest taki, że implementacja ConditionalWeakTable jest 1,8 x szybsza niż implementacja ObjectIDGenerator.

 0
Author: Mr. Ree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:09