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

118

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

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

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

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

Мне нужен уникальный идентификатор для создания хэш-таблицы 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
}
Мартин Коничек
источник

Ответы:

42

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

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

Джон Скит
источник
1
Я предполагаю, что для поиска вам придется перебирать все отслеживаемые вами ссылки: WeakReference на один и тот же объект не равны друг другу, поэтому вы действительно не можете ничего сделать.
Роман Старков
1
В том, чтобы каждому объекту был назначен уникальный 64-битный идентификатор, может быть некоторая полезность, особенно если такие идентификаторы выдавались последовательно. Я не уверен, что полезность оправдает затраты, но такая вещь может быть полезна, если сравнивать два различных неизменяемых объекта и находить их равными; если по возможности заменить ссылку на более новую ссылку на более старую, можно избежать множества избыточных ссылок на идентичные, но разные объекты.
supercat
1
"Идентификатор." Я не думаю, что это слово означает то, что вы думаете.
Слипп Д. Томпсон
5
@ SlippD.Thompson: Нет, это по-прежнему соотношение один к одному. Есть только одно ссылочное значение, которое относится к любому заданному объекту. Это значение может появляться в памяти много раз (например, как значение нескольких переменных), но это все равно одно значение. Это похоже на домашний адрес: я могу записать свой домашний адрес на нескольких листах бумаги, но это все равно идентификатор моего дома. Любые два неидентичных ссылочных значения должны относиться к разным объектам - по крайней мере, в C #.
Джон Скит,
1
@supercat: Я думаю, что мы можем по-разному понимать «инкапсулируемые личности» - но я думаю, что мы также, вероятно, никому не поможем продвинуться дальше, чем мы уже сделали :) Это одна из тем, которую мы должны обсудить подробно, если мы когда-либо встречаемся лично ...
Джон Скит
72

Только .NET 4 и новее

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

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

  • может быть использовано , чтобы связать произвольные данные с управляемыми экземплярами объекта так же, как словарь (хотя это не словарь)
  • не зависит от адресов памяти, поэтому невосприимчив к GC, уплотняющему кучу
  • не поддерживает объекты в живых только потому, что они были введены в качестве ключей в таблицу, поэтому его можно использовать, не заставляя каждый объект в вашем процессе жить вечно
  • использует ссылочное равенство для определения идентичности объекта; moveover, авторы классов не могут изменять это поведение, поэтому его можно последовательно использовать для объектов любого типа
  • можно заполнять на лету, поэтому не требуется вводить код внутри конструкторов объектов
Джон
источник
5
Просто для полноты: ConditionalWeakTableполагается на свою внутреннюю работу RuntimeHelpers.GetHashCodeи object.ReferenceEqualsвыполняет ее. Поведение такое же, как при построении IEqualityComparer<T>, использующего эти два метода. Если вам нужна производительность, я действительно предлагаю это сделать, поскольку ConditionalWeakTableвсе его операции блокируются, чтобы сделать его потокобезопасным.
atlaste 07
1
@StefandeBruijn: A ConditionalWeakTableсодержит ссылку на каждый, Valueкоторая настолько сильна, насколько сильна ссылка в другом месте на соответствующий Key. Объект, на который ConditionalWeakTableхранится единственная существующая ссылка где-либо во вселенной, автоматически перестанет существовать, когда исчезнет ключ.
supercat 07
41

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

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

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

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

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

sisve
источник
5
Reflector сообщает мне, что ObjectIDGenerator - это хэш-таблица, основанная на реализации GetHashCode по умолчанию (т.е. она не использует пользовательские перегрузки).
Антон Тихий
Вероятно, лучшее решение, когда требуются распечатываемые уникальные идентификаторы.
Роман Старков
ObjectIDGenerator также не реализован на телефоне.
Энтони Визер,
Я не совсем понимаю, что делает ObjectIDGenerator, но, похоже, он работает, даже когда он использует RuntimeHelpers.GetHashCode. Я тестировал оба, и только RuntimeHelpers.GetHashCode в моем случае не работает.
Daniel Bişar
+1 - Работает неплохо (по крайней мере, на рабочем столе).
Hot Licks
37

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

Антон Гоголев
источник
2
Это может помочь, но с затратами - IIRC с использованием базового объекта. GetHashCode () должен выделить блок синхронизации, который не является бесплатным. Хорошая идея - +1 от меня.
Джон Скит,
Спасибо, не знал этого метода. Однако он также не создает уникальный хэш-код (ведет себя точно так же, как образец кода в вопросе). Будет полезно, если пользователь переопределит хэш-код, чтобы вызвать версию по умолчанию.
Мартин Коничек
1
Вы можете использовать GCHandle, если вам не нужно их слишком много (см. Ниже).
Антон Тихий
42
В книге по .NET, написанной очень уважаемым автором, говорится, что RuntimeHelpers.GetHashCode () создает код, уникальный в пределах домена приложения, и что Microsoft могла бы назвать метод GetUniqueObjectID. Это просто неправильно. При тестировании я обнаружил, что обычно получаю дубликат к тому времени, когда я создаю 10 000 экземпляров объекта (WinForms TextBox), и никогда не мог пройти мимо 30 000. Код, основанный на предполагаемой уникальности, вызывал периодические сбои в производственной системе после создания не более 1/10 этого количества объектов.
Ян Хеттич
3
@supercat: Ага, только что нашел доказательства из 2003 года, относящиеся к .NET 1.0 и 1.1. Похоже, они планировали что-то изменить для .NET 2: blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
Джон Скит
7

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

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

majkinetor
источник
2
Не поможет, если то, что вам нужно, - это Disposeошибки, потому что это предотвратит любую утилизацию.
Роман Старков
1
Это не совсем работает, поскольку словарь использует равенство вместо идентичности, сворачивая объекты, которые возвращают те же значения для объекта. Equals
Энтони Визер
1
Однако это сохранит объект в живых.
Мартин Лоттеринг
1
@MartinLottering, что, если он использует ConditionalWeakTable <object, idType>?
Деметрис Лептос
7

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

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

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

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

Проблемы?

Dawg
источник
4

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

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

Томас Братт
источник
В каких версиях Visual Studio есть эта функция? Например, экспресс-версии?
Питер Мортенсен
3

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

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

Марк Гравелл
источник
2

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

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

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

Эндрю Текен
источник
1

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

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

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

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

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

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

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

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

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

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

Также следует отметить, что очистка записей должна происходить время от времени. Хотя фактические объекты будут очищены сборщиком мусора, записи - нет. Вот почему ConditionalWeakTableтолько увеличивается в размерах. Как только он достигает определенного предела (определяемого вероятностью столкновения в хэше), он запускает a 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
    }
atlaste
источник
1
A ConditionalWeakTableможет быть лучше, поскольку он будет сохранять только представления для объектов, пока на них существуют ссылки. Кроме того, я бы предположил, что an Int64может быть лучше, чем GUID, поскольку он позволяет присваивать объектам постоянный ранг . Такие вещи могут быть полезны в сценариях блокировки (например, можно избежать взаимоблокировки, если весь код, который должен будет получить несколько блокировок, делает это в некотором определенном порядке, но для того, чтобы это работало, должен быть определенный порядок).
supercat 07
@supercat Конечно насчет longс; это зависит от вашего сценария - в f.ex. с распределенными системами иногда полезнее работать с GUIDs. Что касается ConditionalWeakTable: вы правы; DependentHandleпроверяет живость (ПРИМЕЧАНИЕ: только когда вещь меняет размер!), что может быть здесь полезно. Тем не менее, если вам нужна производительность, блокировка может стать проблемой, так что в этом случае было бы интересно использовать это ... честно говоря, мне лично не нравится реализация ConditionalWeakTable, что, вероятно, приводит к моему предвзятому использованию простого Dictionary- даже хотя ты прав.
atlaste 08
Мне давно интересно, как ConditionalWeakTableэто работает. Тот факт, что он позволяет добавлять только элементы, заставляет меня думать, что он разработан для минимизации накладных расходов, связанных с параллелизмом, но я понятия не имею, как это работает внутри. Мне действительно любопытно, что нет простой DependentHandleоболочки, которая не использует таблицу, так как определенно бывают моменты, когда важно убедиться, что один объект поддерживается в течение всего времени жизни другого, но у последнего объекта нет места для ссылки к первому.
supercat 08
@supercat Я отправлю дополнение о том, как я думаю, что это работает.
atlaste
Эти ConditionalWeakTableданные не позволяют , которые хранились в таблице быть изменены. Таким образом, я думаю, что это можно безопасно реализовать, используя барьеры памяти, но не блокировки. Единственная проблемная ситуация возникнет, если два потока попытаются добавить один и тот же ключ одновременно; эту проблему можно решить, если после добавления элемента выполнить барьер памяти с помощью метода add, а затем выполнить сканирование, чтобы убедиться, что только один элемент имеет этот ключ. Если несколько элементов имеют один и тот же ключ, один из них будет идентифицирован как «первый», поэтому можно будет удалить другие.
supercat 09
0

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

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

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

Во-вторых , предположим, что у вас очень мало объектов, так что 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реализация.

Г-н Ри
источник