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

Представьте, что у меня есть один класс с двумя экземплярами:

MyClass a = new MyClass();
MyClass b = new MyClass();

MyClass имеет метод PrintUniqueInstanceID:

void PrintUniqueInstanceID()
{
  Console.Write("Unique ID for the *instance* of this class: {0}", 
      [what goes here???]
  );
}

В идеале выход будет выглядеть примерно так:

Unique ID for the *instance* of this class: 23439434        // from a.PrintUniqueInstanceID
Unique ID for the *instance* of this class: 89654           // from b.PrintUniqueInstanceID

Итак - что бы я вставлял в "[what goes here???]" выше, который печатает уникальный номер для каждого уникального экземпляра класса?

Идеи

  • Возможно, нарисуйте "this" на указатель int и используйте это?
  • Использовать GCHandle?
  • Доступ к свойству "this", в рамках метода, чтобы однозначно идентифицировать его?

(необязательно) Справочная информация для экспертов

Причина, по которой мне это нужно, это то, что я использую AOP и PostSharp для автоматического обнаружения проблем с потоками. Мне нужно найти каждый уникальный экземпляр класса в словаре, чтобы убедиться, что несколько потоков не обращаются к одному и тому же уникальному экземпляру класса (его нормально, если есть один поток для экземпляра класса).

Обновление

Как отмечали другие, я должен упомянуть, что я не могу тронуть ни один из существующих классов в проекте из 30 000 строк. PrintUniqueInstanceID, выше, является аспектом (см. PostSharp), который добавляется в класс верхнего уровня, наследуется каждым классом во всем проекте и выполняется на каждой записи метода во всем проекте.

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

Ответ 1

Использовать класс ObjectIDGenerator:

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.objectidgenerator.aspx

Цитата:

Идентификаторы уникальны для жизни экземпляра ObjectIDGenerator.

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

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

Обновление

Это код, который решает проблему. В классе аспект используйте следующее:

public static ObjectIDGenerator ObjectIDGen = new ObjectIDGenerator();

то

bool firstTime;
long classInstanceID = ObjectIDGenerator.GetId(args.Instance, out firstTime);

Обновление

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

Полезно, если у вас есть 30k строк существующего кода, и вы хотите добавить более формальную проверку безопасности потоков (что очень сложно сделать обычно). Это влияет на производительность выполнения, поэтому вы можете удалить его после запуска в течение нескольких дней в режиме отладки.

Чтобы использовать, добавьте PostSharp + этот класс в свой проект, а затем добавьте аспект "[MyThreadSafety]" в любой класс. PostSharp будет вставлять код в "OnEntry" перед каждым вызовом метода. Аспект распространяется на все подклассы и под-методы, поэтому вы можете добавить проверки безопасности потока для всего проекта только с одной строкой кода.

Для другого примера этого метода в действии см. пример который позволяет легко добавлять кеширование в вызовы методов.

    using System;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading;
    using MyLogType;
    using PostSharp.Aspects;
    using System.Collections.Concurrent;
    using PostSharp.Extensibility;

    namespace Demo
    {
        /// <summary>
        /// Example code based on the page from a Google search of:
        /// postsharp "Example: Tracing Method Execution"
        /// </summary>
        [Serializable]
        public sealed class MyThreadSafetyCheck : OnMethodBoundaryAspect
        {
            /// <summary>
            /// We need to be able to track if a different ThreadID is seen when executing a method within the *same* instance of a class. Its
            /// ok if we see different ThreadID values when accessing different instances of a class. In fact, creating one copy of a class per
            /// thread is a reliable method to fix threading issues in the first place.
            /// 
            /// Key: unique ID for every instance of every class.
            /// Value: LastThreadID, tracks the ID of the last thread which accessed the current instance of this class.
            /// </summary>
            public static ConcurrentDictionary<long, int> DetectThreadingIssues = new ConcurrentDictionary<long, int>();

            /// <summary>
            /// Allows us to generate a unique ID for each instance of every class that we see.
            /// </summary>
            public static ObjectIDGenerator ObjectIDGenerator = new ObjectIDGenerator();

            /// <summary>
            /// These fields are initialized at runtime. They do not need to be serialized.
            /// </summary>
            [NonSerialized]
            private string MethodName;

            [NonSerialized]
            private long LastTotalMilliseconds;

            /// <summary>
            /// Stopwatch which we can use to avoid swamping the log with too many messages for threading violations.
            /// </summary>
            [NonSerialized]
            private Stopwatch sw;

            /// <summary>
            /// Invoked only once at runtime from the static constructor of type declaring the target method. 
            /// </summary>
            /// <param name="method"></param>
            public override void RuntimeInitialize(MethodBase method)
            {
                if (method.DeclaringType != null)
                {
                    this.MethodName = method.DeclaringType.FullName + "." + method.Name;
                }

                this.sw = new Stopwatch();
                this.sw.Start();

                this.LastTotalMilliseconds = -1000000;
            }

            /// <summary>
            /// Invoked at runtime before that target method is invoked.
            /// </summary>
            /// <param name="args">Arguments to the function.</param>   
            public override void OnEntry(MethodExecutionArgs args)
            {
                if (args.Instance == null)
                {
                    return;
                }

                if (this.MethodName.Contains(".ctor"))
                {
                    // Ignore the thread that accesses the constructor.
                    // If we remove this check, then we get a false positive.
                    return;
                }

                bool firstTime;
                long classInstanceID = ObjectIDGenerator.GetId(args.Instance, out firstTime);

                if (firstTime)
                {
                    // This the first time we have called this, there is no LastThreadID. Return.
                    if (DetectThreadingIssues.TryAdd(classInstanceID, Thread.CurrentThread.ManagedThreadId) == false)
                    {
                        Console.Write(string.Format("{0}Error E20120320-1349. Could not add an initial key to the \"DetectThreadingIssues\" dictionary.\n",
                            MyLog.NPrefix()));
                    }
                    return;
                }

                int lastThreadID = DetectThreadingIssues[classInstanceID];

                // Check 1: Continue if this instance of the class was accessed by a different thread (which is definitely bad).
                if (lastThreadID != Thread.CurrentThread.ManagedThreadId)
                {
                    // Check 2: Are we printing more than one message per second?
                    if ((sw.ElapsedMilliseconds - this.LastTotalMilliseconds) > 1000)
                    {
                        Console.Write(string.Format("{0}Warning: ThreadID {1} then {2} accessed \"{3}\". To remove warning, manually check thread safety, then add \"[MyThreadSafetyCheck(AttributeExclude = true)]\".\n",
                            MyLog.NPrefix(), lastThreadID, Thread.CurrentThread.ManagedThreadId, this.MethodName));
                        this.LastTotalMilliseconds = sw.ElapsedMilliseconds;
                    }
                }

                // Update the value of "LastThreadID" for this particular instance of the class.
                DetectThreadingIssues[classInstanceID] = Thread.CurrentThread.ManagedThreadId;
            }
        }
    }

Я могу предоставить полный демонстрационный проект по запросу.

Ответ 2

Добавьте свойство Guid в свой класс, затем в конструкторе класса назначьте его в NewGuid().

public class MyClass
{
    public Guid InstanceID {get; private set;}
    // Other properties, etc.

    public MyClass()
    {
        this.InstanceID = Guid.NewGuid();
    }

    void PrintUniqueInstanceID() 
    {   
        Console.Write("Unique ID for the *instance* of this class: {0}", this.InstanceID); 
    } 
}

Ответ 3

Пересмотренный ответ

Основываясь на дополнительной информации, которую мы теперь имеем, я считаю, что вы можете легко решить свою проблему с помощью ConditionalWeakTable (который доступен только на .NET 4).

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

Таким образом, вы можете создать такую ​​глобальную таблицу внутри вашего класса "manager" и связать каждый объект с long, a Guid или чем-нибудь еще, что вам может понадобиться¹. Всякий раз, когда ваш менеджер сталкивается с объектом, он может получить свой ассоциированный идентификатор (если вы его видели раньше) или добавить его в таблицу и связать с новым идентификатором, созданным на месте.

¹ Фактически значения в таблице должны быть ссылочного типа, поэтому вы не можете использовать, например. long как тип значения напрямую. Однако простым обходным путем является использование object вместо этого, а также значения long.

Оригинальный ответ

Разве это не основной пример использования для static членов?

class Foo
{
    private static int instanceCounter;
    private readonly int instanceId;

    Foo()
    {
        this.instanceId = ++instanceCounter;
    }

    public int UniqueId
    {
        get { return this.instanceId; }
    }
}

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

Ответ 4

Вы не можете наследовать все свои классы из базового класса или интерфейса и требовать реализации свойства UniqueID?

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

Ответ 5

Может потенциально использовать:

ClassName + MethodName + this.GetHashCode();

Хотя GetHashCode() не гарантирует уникальное значение, если оно сопряжено с именем класса и именем метода, вероятность столкновения уменьшается.

Даже если есть столкновение, единственным эффектом будет то, что он генерирует больше предупреждений в журналах, что не является большой проблемой.