[英]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???]
,為類的每個唯一實例打印一個唯一編號?
主意
(可選)專家背景信息
我需要這個的原因是我正在使用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”類中創建這樣的全局表,並將每個對象與long
, Guid
或您可能想要的其他任何對象相關聯¹。 每當您的經理遇到一個對象時,它都可以獲取其關聯的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.