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

前端 未结 6 1882
谎友^
谎友^ 2021-02-02 13:30

Imagine I have a single class, with two instances:

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

MyClass has a method PrintUniqueInstan

相关标签:
6条回答
  • 2021-02-02 13:53

    Use ObjectIDGenerator class:

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

    Quote:

    The IDs are unique for the life of the ObjectIDGenerator instance.

    Using a hash table, the ObjectIDGenerator retains which ID is assigned to which object. The object references, which uniquely identify each object, are addresses in the runtime garbage-collected heap. Object reference values can change during serialization, but the table is updated automatically so the information is correct.

    Object IDs are 64-bit numbers. Allocation starts from one, so zero is never a valid object ID. A formatter can choose a zero value to represent an object reference whose value is null.

    Update

    This is the code that solves the problem. In the aspect class, use the following:

    public static ObjectIDGenerator ObjectIDGen = new ObjectIDGenerator();
    

    then:

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

    Update

    Thought I'd post the code that this entire post is based on. This code helps to detect thread safety hotspots across an entire project, by triggering warnings if multiple threads access the same instance of a class.

    Useful if you have 30k lines of existing code, and you want to add a more formal verification of thread safety (something which is extremely difficult to do normally). It does impact runtime performance, so you can remove it after running it for a few days in debug mode.

    To use, add PostSharp + this class to your project, then add an aspect "[MyThreadSafety]" to any class. PostSharp will insert the code in "OnEntry" before every method call. The aspect propagates to all sub-classes and sub-methods, so you can add thread safety checks to an entire project with just one line of code.

    For another example of this technique in action, see an example designed to easily add caching to method calls.

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

    I can provide the full demo project on demand.

    0 讨论(0)
  • 2021-02-02 13:57

    An active (not automatic) solution while debugging is to right click on an instance and chose "Make Object ID". It'll append a {$1} next to your instance name and class.

    If later on, you stumble upon another instance, it will be missing that {$1} mark.

    0 讨论(0)
  • 2021-02-02 13:59

    Add a Guid property to your class, then in the constructor of the class assign it to 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); 
        } 
    }
    
    0 讨论(0)
  • 2021-02-02 14:06

    You cannot inherit all your classes from a base class or interface and require implementation of a UniqueID property?

    Another possibility is to wrap them in a class containing a generic object reference and the unique ID, then cataloging them as they are asked for in a lazy way. Cleaning up such a catalog of unique assignments could be awkward.

    0 讨论(0)
  • 2021-02-02 14:14

    Could potentially use:

    ClassName + MethodName + this.GetHashCode();
    

    Although GetHashCode() does not guarantee a unique value, if its paired with the class name and method name, the likelihood of a clash reduces.

    Even if there is a clash, the only effect will be that it generates more warnings in the logs, which isn't a big deal.

    0 讨论(0)
  • 2021-02-02 14:17

    Revised answer

    Based on the additional information we now have, I believe that you can solve your problem very easily using ConditionalWeakTable (which is only from .NET 4 onwards).

    • it can be used to associate arbitrary data with managed object instances
    • it does not keep objects "alive" just because they have been entered as keys into the table
    • it uses reference equality to determine object identity; moveover, class authors cannot modify this behavior (handy since you are not the author of every class on earth)
    • it can be populated on the fly

    You can therefore create such a global table inside your "manager" class and associate each object with a long, a Guid or anything else you might want¹. Whenever your manager encounters an object it can get its associated id (if you have seen it before) or add it to the table and associate it with a new id created on the spot.

    ¹ Actually the values in the table must be of a reference type, so you cannot use e.g. long as the value type directly. However, a simple workaround is to use object instead and let the long values be boxed.

    Original answer

    Isn't this a basic usage example for static members?

    class Foo
    {
        private static int instanceCounter;
        private readonly int instanceId;
    
        Foo()
        {
            this.instanceId = ++instanceCounter;
        }
    
        public int UniqueId
        {
            get { return this.instanceId; }
        }
    }
    

    Of course you have to pay attention to the range of identifiers so that you don't start reusing them if billions of instances are created, but that's easy to solve.

    0 讨论(0)
提交回复
热议问题