我如何获得一个允许我分辨一个类的差异实例的ID?

Con*_*ngo 13 .net c# visual-studio

想象一下,我有一个类,有两个实例:

MyClass a = new MyClass();
MyClass b = new MyClass();
Run Code Online (Sandbox Code Playgroud)

MyClass有一个PrintUniqueInstanceID方法:

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

理想情况下,输出将是这样的:

Unique ID for the *instance* of this class: 23439434        // from a.PrintUniqueInstanceID
Unique ID for the *instance* of this class: 89654           // from b.PrintUniqueInstanceID
Run Code Online (Sandbox Code Playgroud)

那么 - 我将在[what goes here???]上面的" "中插入什么,它会为每个类的唯一实例打印一个唯一的数字?

思路

  1. 也许将"this"转换为int指针,并使用它?
  2. 以某种方式使用GCHandle?
  3. 在方法中访问"this"的属性,以唯一标识它?

(可选)专家背景资料

我需要这个的原因是我使用AOP和PostSharp来自动检测线程问题.我需要在字典中查找类的每个唯一实例,以验证多个线程没有访问类的相同唯一实例(如果每个类实例有一个线程,则确定).

更新

正如其他人所指出的,我应该提到我不能触及30,000线项目中的任何现有类.上面的PrintUniqueInstanceID是一个方面(参见PostSharp),它被添加到顶级类,由整个项目中的每个类继承,并在整个项目中的每个方法条目上执行.

一旦我确认一切都是线程安全的,我将删除方面以恢复性能.

mgn*_*nan 13

将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); 
    } 
}
Run Code Online (Sandbox Code Playgroud)

  • 我会把setter私有化.你为什么要允许为一个实例更改Guid? (3认同)
  • @Gravitas你是不是已经在"Every class"中改变了'PrintUniqueInstanceID`? (3认同)
  • 谢谢你的回答!但是,只有在我想改变整个30,000行项目中的每个类时,这才有效.我需要一些方法来在OnEntry方面唯一地标识类实例,而不需要改变原始类本身. (2认同)

Jon*_*Jon 12

修改后的答案

根据我们现有的其他信息,我相信您可以非常轻松地使用ConditionalWeakTable(仅从.NET 4开始)解决您的问题.

  • 它可用于将任意数据与托管对象实例相关联
  • 它并没有保留的对象"活着",只是因为他们已被输入为键到表
  • 它使用引用相等来确定对象标识; 移动,类作者不能修改这种行为(方便,因为你不是地球上每个类的作者)
  • 它可以在运行中填充

因此,您可以在"manager"类中创建这样一个全局表,并将每个对象与a long,a Guid或您可能需要的任何其他内容相关联.每当您的经理遇到一个对象时,它就可以获得其关联的id(如果您之前已经看过它),或者将其添加到表中并将其与当场创建的新ID相关联.

¹ 实际上表中的值必须是引用类型,因此不能long直接使用eg 作为值类型.但是,一个简单的解决方法是使用object而不是将long值设置为框.

原始答案

这不是static会员的基本用法示例吗?

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

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

    public int UniqueId
    {
        get { return this.instanceId; }
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,您必须注意标识符的范围,以便在创建数十亿个实例时不开始重用它们,但这很容易解决.


Con*_*ngo 6

使用 ObjectIDGenerator 类:

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

引用:

这些 ID 在 ObjectIDGenerator 实例的生命周期内是唯一的。

使用哈希表,ObjectIDGenerator 保留分配给哪个对象的 ID。唯一标识每个对象的对象引用是运行时垃圾收集堆中的地址。对象引用值可以在序列化期间更改,但表会自动更新,因此信息是正确的。

对象 ID 是 64 位数字。分配从 1 开始,因此 0 永远不是有效的对象 ID。格式化程序可以选择零值来表示值为空的对象引用。

更新

这是解决问题的代码。在方面类中,使用以下内容:

public static ObjectIDGenerator ObjectIDGen = new ObjectIDGenerator();
Run Code Online (Sandbox Code Playgroud)

然后:

bool firstTime;
long classInstanceID = ObjectIDGenerator.GetId(args.Instance, out firstTime);
Run Code Online (Sandbox Code Playgroud)

更新

以为我会发布整篇文章所基于的代码。此代码通过在多个线程访问类的同一实例时触发警告来帮助检测整个项目中的线程安全热点。

如果您有 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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

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