如何获得允许我区分类的不同实例的ID
本文关键字:实例 ID 允许我区 分类 何获得 | 更新日期: 2023-09-27 18:28:27
想象一下,我有一个类,有两个实例:
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来自动检测线程问题。我需要在字典中查找该类的每个唯一实例,以验证多个线程是否正在访问一个类的同一个唯一实例(如果每个类实例有一个线程,则可以)。
更新
正如其他人所指出的,我应该提到的是,我不能接触30000线项目中的任何现有类。上面的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开始)非常容易地解决您的问题。
- 它可以用于将任意数据与托管对象实例相关联
- 它不会仅仅因为对象已作为键输入到表中就保持对象"活动"
- 它使用引用相等来确定对象身份;moveover,类作者不能修改此行为(很方便,因为你不是地球上每个类的作者)
- 它可以在飞行中填充
因此,您可以在"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/en-us/library/system.runtime.serialization.objectidgenerator.aspx
报价:
ID在ObjectIDGenerator实例的生命周期内是唯一的。
ObjectIDGenerator使用哈希表来保留分配的ID到哪个对象。对象引用,唯一标识每个对象,是运行时垃圾收集堆中的地址。对象引用值可以在序列化期间更改,但表自动更新,使信息正确。
对象ID是64位数字。分配从一开始,所以零是从来都不是有效的对象ID。格式化程序可以选择零值表示值为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;
}
}
}
我可以根据需要提供完整的演示项目。
调试时的一个活动(非自动)解决方案是右键单击实例并选择"生成对象ID"。它将在实例名称和类旁边附加一个{$1}
。
如果稍后偶然发现另一个实例,它将缺少{$1}
标记。
您不能从基类或接口继承所有类并要求实现UniqueID属性吗?
另一种可能性是将它们封装在一个包含通用对象引用和唯一ID的类中,然后以惰性的方式按照要求对它们进行编目。清理这样一个由独特作业组成的目录可能会很尴尬。
可能使用:
ClassName + MethodName + this.GetHashCode();
虽然GetHashCode()不能保证一个唯一的值,但如果它与类名和方法名配对,冲突的可能性就会降低。
即使发生冲突,唯一的效果就是在日志中生成更多警告,这没什么大不了的。