Вопрос

Есть ли способ получить уникальный идентификатор экземпляра?

GetHashCode() то же самое для двух ссылок, указывающих на один и тот же экземпляр.Однако два разных экземпляра могут (довольно легко) получить один и тот же хэш-код:

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

Я пишу отладочную надстройку, и мне нужно получить какой-то идентификатор для ссылки, который является уникальным во время запуска программы.

Мне уже удалось получить внутренний АДРЕС экземпляра, который уникален до тех пор, пока сборщик мусора (GC) не уплотнит кучу (= перемещает объекты = изменяет адреса).

Вопрос о переполнении стека Реализация по умолчанию для объекта.GetHashCode() возможно, это связано.

Объекты не находятся под моим контролем, поскольку я получаю доступ к объектам в отлаживаемой программе с помощью debugger API.Если бы я контролировал объекты, добавление моих собственных уникальных идентификаторов было бы тривиальным.

Мне нужен был уникальный идентификатор для создания идентификатора хэш-таблицы -> object, чтобы иметь возможность искать уже виденные объекты.На данный момент я решил это следующим образом:

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
}
Это было полезно?

Решение

Ссылка является уникальный идентификатор объекта.Я не знаю никакого способа преобразовать это во что-то вроде строки и т.д.Значение ссылки будет меняться во время сжатия (как вы видели), но каждое предыдущее значение A будет изменено на значение B, поэтому с точки зрения безопасного кода это по-прежнему уникальный идентификатор.

Если задействованные объекты находятся под вашим контролем, вы можете создать сопоставление, используя слабые ссылки (чтобы избежать предотвращения сбора мусора) из ссылки на выбранный вами идентификатор (GUID, целое число и т. д.).Однако это добавит определенные накладные расходы и сложность.

Другие советы

Только .NET 4 и более поздние версии

Хорошие новости всем!

Идеальный инструмент для этой работы встроен в .NET 4 и называется ConditionalWeakTable<TKey, TValue>.Этот класс:

  • может использоваться для связи произвольных данных с экземплярами управляемых объектов, подобно словарю (хотя это является это не словарь)
  • не зависит от адресов памяти, поэтому невосприимчив к сжатию кучи сборщиком мусора
  • не сохраняет объекты живыми только потому, что они были введены в качестве ключей в таблицу, поэтому его можно использовать, не делая каждый объект в вашем процессе живым навсегда
  • использует равенство ссылок для определения идентичности объекта;перемещение, авторы классов не могут изменить это поведение, чтобы его можно было использовать последовательно на объектах любого типа
  • может заполняться на лету, поэтому не требует внедрения кода внутри конструкторов объектов

Проверил Генератор ObjectIDGenerator сорт?Это делает то, что вы пытаетесь сделать, и то, что описывает Марк Грэвелл.

ObjectIDGenerator отслеживает ранее идентифицированные объекты.Когда вы запрашиваете идентификатор объекта, ObjectIDGenerator знает, следует ли вернуть существующий идентификатор или сгенерировать и запомнить новый идентификатор.

Идентификаторы уникальны на протяжении всего срока существования экземпляра ObjectIDGenerator.Как правило, срок службы ObjectIDGenerator длится столько же, сколько и форматтер, создавший его.Идентификаторы объектов имеют значение только внутри данного сериализованного потока и используются для отслеживания того, какие объекты имеют ссылки на другие в графе сериализованных объектов.

Используя хеш-таблицу, ObjectIDGenerator сохраняет, какой идентификатор какому объекту присвоен.Ссылки на объекты, которые однозначно идентифицируют каждый объект, представляют собой адреса в куче, собираемой мусором во время выполнения.Значения ссылок на объекты могут измениться во время сериализации, но таблица обновляется автоматически, поэтому информация верна.

Идентификаторы объектов представляют собой 64-битные числа.Распределение начинается с единицы, поэтому ноль никогда не является допустимым идентификатором объекта.Средство форматирования может выбрать нулевое значение для представления ссылки на объект, значение которой является нулевой ссылкой (Nothing в Visual Basic).

RuntimeHelpers.GetHashCode() может поможет(MSDN).

Вы можете разработать что-то свое за секунду.Например:

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

Вы можете самостоятельно выбрать уникальный идентификатор, например System.Guid.NewGuid() или просто целое число для быстрого доступа.

Как насчет этого метода:

Установите для поля в первом объекте новое значение.Если то же поле во втором объекте имеет то же значение, вероятно, это тот же экземпляр.В противном случае выйдите как другое.

Теперь установите для поля в первом объекте другое новое значение.Если то же поле во втором объекте изменилось на другое значение, это определенно тот же экземпляр.

Не забудьте вернуть поле в первом объекте к исходному значению при выходе.

Проблемы?

В Visual Studio можно сделать уникальный идентификатор объекта:В окне просмотра щелкните правой кнопкой мыши переменную объекта и выберите Создать идентификатор объекта из контекстного меню.

К сожалению, это делается вручную, и я не верю, что к идентификатору можно получить доступ через код.

Вам придется назначить такой идентификатор самостоятельно, вручную — либо внутри экземпляра, либо извне.

Для записей, относящихся к базе данных, первичный ключ может быть полезен (но вы все равно можете получить дубликаты).Альтернативно, либо используйте Guid, или сохраните свой собственный счетчик, распределив его с помощью Interlocked.Increment (и сделайте его достаточно большим, чтобы избежать переполнения).

Я знаю, что на этот вопрос был дан ответ, но, по крайней мере, полезно отметить, что вы можете использовать:

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

Это не даст вам «уникальный идентификатор» напрямую, но в сочетании с WeakReferences (и хэш-набором?) может дать вам довольно простой способ отслеживания различных экземпляров.

Информация, которую я здесь привожу, не нова, я просто добавил это для полноты картины.

Идея этого кода довольно проста:

  • Объектам нужен уникальный идентификатор, которого по умолчанию нет.Вместо этого мы должны полагаться на следующую лучшую вещь, которая является RuntimeHelpers.GetHashCode чтобы получить нам своего рода уникальный идентификатор
  • Чтобы проверить уникальность, это подразумевает, что нам нужно использовать object.ReferenceEquals
  • Тем не менее, мы все равно хотели бы иметь уникальный идентификатор, поэтому я добавил GUID, который по определению уникален.
  • Поскольку мне не нравится все блокировать, если в этом нет необходимости, я не использую ConditionalWeakTable.

В совокупности это даст вам следующий код:

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

Чтобы использовать его, создайте экземпляр UniqueIdMapper и используйте GUID, который он возвращает для объектов.


Добавление

Итак, здесь происходит еще кое-что;позвольте мне немного написать о ConditionalWeakTable.

ConditionalWeakTable делает пару вещей.Самое главное, что он не заботится о сборщике мусора, то есть:объекты, на которые вы ссылаетесь в этой таблице, будут собраны независимо от этого.Если вы выполняете поиск по объекту, он в основном работает так же, как приведенный выше словарь.

Любопытно, нет?В конце концов, когда GC собирает объект, он проверяет, есть ли ссылки на объект, и если есть, он собирает их.Итак, если есть объект из ConditionalWeakTable, почему тогда будет собран объект, на который ссылается ссылка?

ConditionalWeakTable использует небольшой трюк, который также используют некоторые другие структуры .NET:вместо того чтобы хранить ссылку на объект, он фактически хранит IntPtr.Поскольку это не реальная ссылка, объект может быть собран.

Итак, на данный момент необходимо решить 2 проблемы.Во-первых, объекты можно перемещать по куче, так что же мы будем использовать в качестве IntPtr?И, во-вторых, откуда мы знаем, что объекты имеют активную ссылку?

  • Объект может быть закреплен в куче, и его реальный указатель может быть сохранен.Когда GC обращается к объекту для удаления, он открепляет его и собирает.Однако это означало бы, что мы получаем закрепленный ресурс, что не очень хорошая идея, если у вас много объектов (из-за проблем с фрагментацией памяти).Вероятно, это работает не так.
  • Когда GC перемещает объект, он выполняет обратный вызов, который затем может обновить ссылки.Возможно, именно так это реализовано, судя по внешним вызовам в DependentHandle - но я считаю, что это немного сложнее.
  • Сохраняется не указатель на сам объект, а указатель в списке всех объектов из GC.IntPtr - это либо индекс, либо указатель в этом списке.Список изменяется только тогда, когда объект меняет поколения, и в этот момент простой обратный вызов может обновить указатели.Если вы помните, как работает Mark & Sweep, это имеет больше смысла.Закрепления нет, и удаление происходит так же, как и раньше.Я верю, что именно так это работает в DependentHandle.

Это последнее решение требует, чтобы среда выполнения не использовала повторно сегменты списка до тех пор, пока они не будут явно освобождены, а также требует, чтобы все объекты извлекались вызовом среды выполнения.

Если мы предположим, что они используют это решение, мы также сможем решить вторую проблему.Алгоритм Mark & Sweep отслеживает, какие объекты были собраны;на данный момент мы знаем, как только он будет собран.Как только объект проверяет, есть ли объект, он вызывает 'Free', который удаляет указатель и запись списка.Объект действительно исчез.

Одна важная вещь, которую следует отметить на этом этапе, заключается в том, что все идет ужасно неправильно, если ConditionalWeakTable обновляется в нескольких потоках, и если это не потокобезопасно.Результатом будет утечка памяти.Вот почему все звонки в ConditionalWeakTable сделайте простую "блокировку", которая гарантирует, что этого не произойдет.

Еще одна вещь, которую следует отметить, - это то, что очистка записей должна происходить время от времени.В то время как фактические объекты будут очищены GC, записи - нет.Вот почему ConditionalWeakTable только увеличивается в размерах.Как только он достигает определенного предела (определяемого вероятностью столкновения в хэше), он запускает Resize, который проверяет, должны ли объекты быть очищены - если они это делают, free вызывается в процессе GC, удаляя IntPtr ручка.

Я полагаю, что именно поэтому DependentHandle не предоставляется напрямую - вы же не хотите возиться с вещами и в результате получить утечку памяти.Следующая лучшая вещь для этого - это WeakReference (который также хранит IntPtr вместо объекта) - но, к сожалению, не включает аспект "зависимости".

Вам остается только поиграться с механикой, чтобы увидеть зависимость в действии.Обязательно запустите его несколько раз и понаблюдайте за результатами:

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
    }

Если вы пишете модуль в своем собственном коде для конкретного использования, метод майкинетора МОЩЬ работал.Но есть некоторые проблемы.

Первый, официальный документ делает НЕТ гарантировать, что GetHashCode() возвращает уникальный идентификатор (см. Метод Object.GetHashCode()):

Не следует предполагать, что равные хеш-коды подразумевают равенство объектов.

Второй, предположим, что у вас очень небольшое количество объектов, так что GetHashCode() будет работать в большинстве случаев, этот метод может быть переопределен некоторыми типами.
Например, вы используете некоторый класс C, и он переопределяет GetHashCode() всегда возвращать 0.Тогда каждый объект C получит один и тот же хеш-код.К сожалению, Dictionary, HashTable и некоторые другие ассоциативные контейнеры будут использовать этот метод:

Хэш-код — это числовое значение, которое используется для вставки и идентификации объекта в коллекции на основе хэша, такой как класс Dictionary<TKey, TValue>, класс Hashtable или тип, производный от класса DictionaryBase.Метод GetHashCode предоставляет этот хеш-код для алгоритмов, которым требуется быстрая проверка равенства объектов.

Итак, этот подход имеет большие ограничения.

И даже больше, А что, если вы хотите создать библиотеку общего назначения?Вы не только не сможете модифицировать исходный код используемых классов, но их поведение еще и непредсказуемо.

я ценю это Джон и Саймон опубликовали свои ответы, а ниже я опубликую пример кода и предложения по производительности.

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

В моем тесте ObjectIDGenerator выдаст исключение, чтобы пожаловаться на слишком много объектов при создании 10 000 000 объектов (в 10 раз больше, чем в приведенном выше коде) в for петля.

Кроме того, результат теста заключается в том, что ConditionalWeakTable реализация в 1,8 раза быстрее, чем ObjectIDGenerator выполнение.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top