簡體   English   中英

如何獲得一個ID,該ID可以區分一個類的不同實例?

[英]How do I obtain an ID that allows me to tell difference instances of a class apart?

想象一下,我有一個類,有兩個實例:

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???]插入[what goes here???] ,為類的每個唯一實例打印一個唯一編號?

主意

  1. 也許將“ this”轉換為int指針,並使用它?
  2. 是否以某種方式使用GCHandle?
  3. 在方法內訪問“ this”的屬性以唯一地標識它?

(可選)專家背景信息

我需要這個的原因是我正在使用AOP和PostSharp自動檢測線程問題。 我需要在字典中查找類的每個唯一實例,以驗證多個線程沒有訪問一個類的相同唯一實例(如果每個類實例有一個線程,則可以)。

更新資料

正如其他人指出的那樣,我應該提到,我無法觸及30,000行項目中的任何現有課程。 上面的PrintUniqueInstanceID是一個方面(請參閱PostSharp ),它已添加到頂級類,被整個項目中的每個類繼承,並在整個項目中的每個方法項上執行。

一旦確認所有內容都是線程安全的,我將刪除該方面以恢復性能。

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

修改后的答案

基於我們現在擁有的其他信息,我相信您可以使用ConditionalWeakTable僅從.NET 4開始 )非常輕松地解決您的問題。

  • 它可用於將任意數據與托管對象實例相關聯
  • 保留對象的“活着”,只是因為他們已被輸入為鍵到表
  • 它使用引用相等性來確定對象身份; 移動時,班級作者無法修改此行為(方便,因為您不是地球上每個班級的作者)
  • 可以即時填充

因此,您可以在“ manager”類中創建這樣的全局表,並將每個對象與longGuid或您可能想要的其他任何對象相關聯¹。 每當您的經理遇到一個對象時,它都可以獲取其關聯的ID(如果您以前曾見過)或將其添加到表中,並將其與當場創建的新ID關聯。

¹ 實際上,表中的值必須是引用類型,因此不能直接使用例如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; }
    }
}

當然,您必須注意標識符的范圍,這樣,如果創建了數十億個實例,就不會開始重用它們,但這很容易解決。

使用ObjectIDGenerator類:

http://msdn.microsoft.com/zh-CN/library/system.runtime.serialization.objectidgenerator.aspx

引用:

這些ID在ObjectIDGenerator實例的生存期內是唯一的。

使用哈希表,ObjectIDGenerator保留將哪個ID分配給哪個對象。 唯一標識每個對象的對象引用是運行時垃圾收集堆中的地址。 對象引用值可以在序列化過程中更改,但是表會自動更新,因此信息正確。

對象ID是64位數字。 分配從1開始,因此零永遠不是有效的對象ID。 格式化程序可以選擇一個零值來表示其值為null的對象引用。

更新資料

這是解決問題的代碼。 在方面類中,使用以下內容:

public static ObjectIDGenerator ObjectIDGen = new ObjectIDGenerator();

然后:

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

更新資料

以為我會發布整個帖子所基於的代碼。 如果多個線程訪問一個類的同一實例,則通過觸發警告,此代碼有助於檢測整個項目中的線程安全熱點。

如果您有3萬行現有代碼,並且想要添加更正式的線程安全性驗證(通常很難做到這一點),則很有用。 它確實會影響運行時性能,因此您可以在調試模式下運行幾天后將其刪除。

要使用,請將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;
            }
        }
    }

我可以按需提供完整的演示項目。

調試時一個有效的(非自動)解決方案是右鍵單擊一個實例,然后選擇“ Make Object ID”。 它將在實例名稱和類旁邊附加一個{$1}

如果稍后,您偶然發現另一個實例,它將丟失那個{$1}標記。

您不能從基類或接口繼承所有類,並且需要實現UniqueID屬性嗎?

另一種可能性是將它們包裝在包含通用對象引用和唯一ID的類中,然后按照懶惰的方式對它們進行分類。 清理這樣的唯一任務目錄可能很尷尬。

可能使用:

ClassName + MethodName + this.GetHashCode();

盡管GetHashCode()不保證唯一值,但如果將其與類名和方法名配對,則發生沖突的可能性會降低。

即使發生沖突,唯一的效果是它將在日志中生成更多警告,這沒什么大不了的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM