C# 我如何获得一个 ID 来区分一个类的不同实例?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9788680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 08:52:46  来源:igfitidea点击:

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

c#.netvisual-studio

提问by Contango

Imagine I have a single class, with two instances:

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

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

MyClass has a method PrintUniqueInstanceID:

MyClass 有一个方法 PrintUniqueInstanceID:

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

Ideally, the output would be something like:

理想情况下,输出将类似于:

Unique ID for the *instance* of this class: 23439434        // from a.PrintUniqueInstanceID
Unique ID for the *instance* of this class: 89654           // from b.PrintUniqueInstanceID

So - what would I insert in "[what goes here???]", above, which prints a unique number for every unique instance of the class?

那么 - 我会在[what goes here???]上面的“ ”中插入什么,它为类的每个唯一实例打印一个唯一编号?

Ideas

想法

  1. Perhaps cast "this" to an int pointer, and use that?
  2. Use GCHandle somehow?
  3. Access a property of "this", within the method, to uniquely identify it?
  1. 也许将“this”转换为 int 指针,然后使用它?
  2. 以某种方式使用 GCHandle?
  3. 在方法中访问“this”的属性以唯一标识它?

(optional) Background Information For Experts

(可选)专家背景信息

The reason I need this is that I am using AOP and PostSharp to automatically detect threading issues. I need to look up each unique instance of the class in a dictionary, in order to verify that multiple threads are not accessing the same unique instance of a class (its ok if there is one thread per class instance).

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

Update

更新

As others have pointed out, I should mention that I can't touch any of the existing classes in the 30,000 line project. PrintUniqueInstanceID, above, is an aspect (see PostSharp) that is added to the top level class, is inherited by every class in the entire project, and executes on every method entry in the entire project.

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

Once I have verified that everything is thread safe, I'll remove the aspect to restore performance.

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

采纳答案by Contango

Use ObjectIDGenerator class:

使用 ObjectIDGenerator 类:

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

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.

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

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

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

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.

如果您有 30k 行现有代码,并且您想添加更正式的线程安全验证(通常很难做到这一点),则很有用。它确实会影响运行时性能,因此您可以在调试模式下运行几天后将其删除。

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.

要使用,请将 PostSharp + 此类添加到您的项目中,然后向任何类添加方面“[MyThreadSafety]”。PostSharp 将在每次方法调用之前在“OnEntry”中插入代码。该方面传播到所有子类和子方法,因此您只需一行代码就可以向整个项目添加线程安全检查。

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.

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

回答by Jon

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).

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

  • it can be used to associate arbitrary data with managed object instances
  • it does notkeep objects "alive" just because they have been entered as keys into the table
  • it uses reference equalityto determine object identity; moveover, class authors cannotmodify 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 Guidor anything else you might want1. 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.

因此,您可以在“管理器”类中创建这样一个全局表,并将每个对象与 a long、 aGuid或您可能想要的任何其他内容相关联。每当您的经理遇到一个对象时,它可以获取其关联的 id(如果您以前见过它)或将其添加到表中并将其与当场创建的新 id 相关联。

1 Actually the values in the table must be of a reference type, so you cannot use e.g. longas the value type directly. However, a simple workaround is to use objectinstead and let the longvalues be boxed.

1实际上表中的值必须是引用类型,所以不能long直接使用eg作为值类型。但是,一个简单的解决方法是object改用并让long值装箱。

Original answer

原答案

Isn't this a basic usage example for staticmembers?

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

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.

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

回答by mgnoonan

Add a Guid property to your class, then in the constructor of the class assign it to NewGuid().

向类添加 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); 
    } 
}

回答by Cade Roux

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

您不能从基类或接口继承所有类并需要实现 UniqueID 属性吗?

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.

另一种可能性是将它们包装在一个包含通用对象引用和唯一 ID 的类中,然后在需要时以惰性方式对它们进行编目。清理这样一个独特任务的目录可能很尴尬。

回答by Contango

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.

虽然 GetHashCode() 不保证唯一值,但如果将其与类名和方法名配对,冲突的可能性就会降低。

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.

即使发生冲突,唯一的影响是它会在日志中生成更多警告,这没什么大不了的。

回答by jeromej

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.

调试时的一个主动(非自动)解决方案是右键单击一个实例并选择“Make Object ID”。它将{$1}在您的实例名称和类旁边附加一个。

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

如果稍后,您偶然发现另一个实例,它将丢失该{$1}标记。