Уникальный идентификатор объекта .NET

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

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) не уплотняет кучу (= перемещает объекты = меняет адреса).

Stack вопрос с переполнением Возможно, была связана с реализацией по умолчанию для Object.GetHashCode().

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

Мне нужен уникальный идентификатор для создания объекта hashtable ID → , чтобы иметь возможность искать уже увиденные объекты. Пока я решил это так:

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
}

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

   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() или просто целое для быстрого доступа.

Ответ 6

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

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

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

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

Проблемы?

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

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

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

  • Объектам нужен уникальный идентификатор, которого по умолчанию нет. Вместо этого мы должны полагаться на следующее лучшее, что есть 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 and 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
    }

Ответ 11

Если вы пишете модуль в своем собственном коде для определенного использования, majkinetor method MIGHT сработали. Но есть некоторые проблемы.

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

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

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

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

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

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

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

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.