.NET独特的对象标识符

有没有办法获得一个实例的唯一标识符?

对于指向同一个实例的两个引用, GetHashCode()是相同的。 但是,两个不同的实例可以(非常容易地)获得相同的哈希码:

 Hashtable hashCodesSeen = new Hashtable(); LinkedList<object> l = new LinkedList<object>(); int n = 0; while (true) { object o = new object(); // Remember objects so that they don't get collected. // This does not make any difference though :( l.AddFirst(o); int hashCode = o.GetHashCode(); n++; if (hashCodesSeen.ContainsKey(hashCode)) { // Same hashCode seen twice for DIFFERENT objects (n is as low as 5322). Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")"); break; } hashCodesSeen.Add(hashCode, null); } 

我正在编写一个debugging插件,并且我需要获得某种ID的引用,这个引用在程序运行过程中是唯一的。

我已经设法得到实例的内部ADDRESS,这是唯一的,直到垃圾收集器(GC)压缩堆(=移动对象=改变地址)。

堆栈溢出问题Object.GetHashCode()的默认实现可能是相关的。

这些对象不在我的控制之下,因为我使用debugging器API访问正在debugging的程序中的对象。 如果我在控制对象,添加我自己的唯一标识符将是微不足道的。

我想build立一个哈希表ID – >对象的唯一ID,以便能够查找已经看到的对象。 现在我解决了这个问题:

 Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode') Find if object seen(o) { candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode. If no candidates, the object is new If some candidates, compare their addresses to o.Address If no address is equal (the hash code was just a coincidence) -> o is new If some address equal, o already seen } 

引用对象的唯一标识符。 我不知道有什么方法将它转换成任何类似于string的东西。引用的值在压缩过程中会改变(正如你所看到的),但是每个以前的值A都会被改变为值B,所以到目前为止作为安全代码而言,它仍然是唯一的ID。

如果所涉及的对象在您的控制之下,您可以使用弱引用 (避免阻止垃圾回收)从您select的ID(GUID,整数,无论​​)的引用来创build映射。 然而,这会增加一定的开销和复杂性。

.NET 4和更高版本

好消息,大家!

这个工作的完美工具是在.NET 4中build立的,它被称为ConditionalWeakTable<TKey, TValue> 。 这个class:

  • 可以用来将任意数据与托pipe对象实例关联起来,就像字典一样(尽pipe它不是字典)
  • 不依赖于内存地址,所以不需要GC压缩堆
  • 不会仅仅因为它们被作为键input到表中而使对象保持活动状态,所以它可以被使用而不会让你的进程中的每个对象都永远活着
  • 使用参考平等来确定对象身份; 移动,类作者不能修改这个行为,所以它可以在任何types的对象上一致使用
  • 可以随时进行填充,因此不需要在对象构造函数中注入代码

检出了ObjectIDGenerator类? 这就是你想要做的,以及Marc Gravell所描述的。

ObjectIDGenerator跟踪以前识别的对象。 当您询问对象的ID时,ObjectIDGenerator知道是返回现有的ID还是生成并记住一个新的ID。

这些ID在ObjectIDGenerator实例的生命周期中是唯一的。 通常,ObjectIDGenerator的生命周期与创build它的Formatter一样长。 对象ID仅在给定的序列化stream中具有含义,并且用于跟踪哪些对象在序列化的对象图中具有对其他对象的引用。

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

对象ID是64位数字。 分配从一个开始,所以零不会是一个有效的对象ID。 格式化程序可以select一个零值来表示其值为空引用的对象引用(在Visual Basic中为Nothing)。

RuntimeHelpers.GetHashCode()可能有所帮助( MSDN )。

你可以在一秒钟内开发自己的东西。 例如:

  class Program { static void Main(string[] args) { var a = new object(); var b = new object(); Console.WriteLine("", a.GetId(), b.GetId()); } } public static class MyExtensions { //this dictionary should use weak key references static Dictionary<object, int> d = new Dictionary<object,int>(); static int gid = 0; public static int GetId(this object o) { if (d.ContainsKey(o)) return d[o]; return d[o] = gid++; } } 

您可以select自己想要的唯一ID,例如System.Guid.NewGuid()或简单的整数以获得最快的访问权限。

这个方法如何:

将第一个对象中的字段设置为新值。 如果第二个对象中的相同字段具有相同的值,则可能是同一个实例。 否则,退出不同的。

现在将第一个对象中的字段设置为不同的新值。 如果第二个对象中的同一个字段已经改变为不同的值,那么肯定是同一个实例。

不要忘记在退出时将第一个对象中的字段设置为原始值。

问题?

可以在Visual Studio中创build唯一的对象标识符:在监视窗口中,右键单击对象variables,然后从上下文菜单中select“创build对象标识 ”。

不幸的是,这是一个手动步骤,我不相信标识符可以通过代码访问。

您必须自己手动分配这样一个标识符,无论是在实例内还是外部。

对于与数据库相关的logging,主键可能是有用的(但仍然可以得到重复)。 另外,要么使用Guid ,要么保留自己的计数器,使用Interlocked.Increment进行分配(并使其足够大以至于不可能溢出)。

我知道这已经得到了回答,但至less有用的是要注意,您可以使用:

http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx

哪个不会直接给你一个“唯一的id”,但是与WeakReferences(和一个hashset?)结合起来可以给你一个很简单的跟踪各种实例的方法。

我在这里给出的信息并不新,我只是为了完整而添加了这些信息。

这个代码的想法很简单:

  • 对象需要一个唯一的ID,这在默认情况下是不存在的。 相反,我们不得不依靠下一个最好的事情,即RuntimeHelpers.GetHashCode来获得一个唯一的ID
  • 为了检查唯一性,这意味着我们需要使用object.ReferenceEquals
  • 但是,我们仍然希望拥有一个唯一的ID,所以我添加了一个GUID ,这个GUID在定义上是唯一的。
  • 因为我不喜欢locking一切,如果我不必,我不使用ConditionalWeakTable

结合起来,这会给你下面的代码:

 public class UniqueIdMapper { private class ObjectEqualityComparer : IEqualityComparer<object> { public bool Equals(object x, object y) { return object.ReferenceEquals(x, y); } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer()); public Guid GetUniqueId(object o) { Guid id; if (!dict.TryGetValue(o, out id)) { id = Guid.NewGuid(); dict.Add(o, id); } return id; } } 

要使用它,创build一个UniqueIdMapper的实例并使用它返回的对象的GUID。


附录

所以,这里还有更多的事情要做。 让我写一下关于ConditionalWeakTable

ConditionalWeakTable做了一些事情。 最重要的是,它并不关心垃圾收集器,也就是说,不pipe你在这个表中引用的对象是什么,都会被收集起来。 如果你查找一个对象,它基本上和上面的字典一样。

好奇不? 毕竟,当一个对象被GC收集时,它会检查是否有对象的引用,如果有的话,它会收集它们。 那么如果ConditionalWeakTable有一个对象,为什么要收集这个被引用的对象呢?

ConditionalWeakTable使用了一些小技巧,其他一些.NET结构也使用它:不是存储对象的引用,而是实际存储一个IntPtr。 因为这不是一个真正的参考,可以收集对象。

所以,现在有两个问题需要解决。 首先,对象可以在堆上移动,那么我们将如何使用IntPtr? 其次,我们如何知道对象具有主动参考?

  • 该对象可以固定在堆上,并且可以存储它的实际指针。 当GC击中要移除的对象时,将其取下并收集起来。 但是,这意味着我们得到一个固定的资源,如果你有很多的对象(由于内存碎片问题),这不是一个好主意。 这可能不是如何工作。
  • 当GC移动一个对象时,它会callback,然后可以更新引用。 这可能是通过DependentHandle的外部调用来实现的 – 但我相信它稍微复杂一些。
  • 不是指向对象本身的指针,而是存储来自GC的所有对象列表中的指针。 IntPtr是这个列表中的索引或指针。 只有当一个对象改变了一代时,这个列表才会改变,在这一点上,一个简单的callback可以更新指针。 如果你还记得Mark&Sweep是如何工作的,那么这样做更有意义。 没有固定,拆除和以前一样。 我相信这是如何在DependentHandle

最后一个解决scheme确实要求运行时不会重复使用列表桶,直到它们被显式释放为止,并且还要求通过调用运行时来检索所有对象。

如果我们假设他们使用这个解决scheme,我们也可以解决第二个问题。 标记和扫描algorithm跟踪哪些对象被收集; 一旦收集完毕,我们就知道这一点。 一旦对象检查对象是否在那里,它会调用“Free”,它将删除指针和列表项。 对象真的没了。

在这一点上需要注意的一件重要事情是,如果ConditionalWeakTable在多个线程中更新并且不是线程安全的,那么事情就会变得非常糟糕。 结果将是一个内存泄漏。 这就是为什么ConditionalWeakTable所有调用都会执行一个简单的“locking”,以确保不会发生这种情况。

另一件需要注意的事情是,清理条目必须偶尔发生。 虽然实际的对象将被GC清理,但条目不是。 这就是为什么ConditionalWeakTable只能增长的原因。 一旦它达到了一定的限制(由hash中的碰撞机会决定),它会触发一个Resize ,它检查对象是否需要清理 – 如果是,则在GC进程中调用free ,移除IntPtr句柄。

我相信这也是为什么DependentHandle没有直接暴露的原因 – 你不想惹事情,并因此导致内存泄漏。 下一个最好的事情是WeakReference (它也存储一个IntPtr而不是一个对象) – 但不幸的是不包括“依赖”方面。

还有什么是你玩弄机械的,所以你可以看到在实际中的依赖。 一定要多次启动并观察结果:

 class DependentObject { public class MyKey : IDisposable { public MyKey(bool iskey) { this.iskey = iskey; } private bool disposed = false; private bool iskey; public void Dispose() { if (!disposed) { disposed = true; Console.WriteLine("Cleanup {0}", iskey); } } ~MyKey() { Dispose(); } } static void Main(string[] args) { var dep = new MyKey(true); // also try passing this to cwt.Add ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>(); cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex. GC.Collect(GC.MaxGeneration); GC.WaitForFullGCComplete(); Console.WriteLine("Wait"); Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there } 

如果你正在自己的代码中编写一个模块以用于特定的用法, majkinetor的方法可能已经起作用了。 但是有一些问题。

首先 ,官方文档不保证GetHashCode()返回一个唯一的标识符(参见Object.GetHashCode方法() ):

你不应该假定相同的哈希码意味着对象相等。

其次 ,假设你有一个非常less量的对象,所以GetHashCode()将在大多数情况下工作,这种方法可以被某些types覆盖。
例如,您正在使用某个类C,并且它会覆盖GetHashCode()始终返回0.然后,C的每个对象都将获得相同的哈希码。 不幸的是, DictionaryHashTable和其他一些关联容器会使用这个方法:

哈希码是一个数字值,用于在基于哈希的集合(如Dictionary <TKey,TValue>),Hashtable类或从DictionaryBase类派生的types中插入和标识对象。 GetHashCode方法为需要快速检查对象相等性的algorithm提供了这个哈希码。

所以这种方法有很大的局限性。

甚至更多 ,如果你想build立一个通用库? 你不但不能修改使用的类的源代码,而且它们的行为也是不可预知的。

我很欣赏乔恩和西蒙已经发布了他们的答案,我将在下面发布代码示例和性能方面的build议。

 using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Collections.Generic; namespace ObjectSet { public interface IObjectSet { /// <summary> check the existence of an object. </summary> /// <returns> true if object is exist, false otherwise. </returns> bool IsExist(object obj); /// <summary> if the object is not in the set, add it in. else do nothing. </summary> /// <returns> true if successfully added, false otherwise. </returns> bool Add(object obj); } public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet { /// <summary> unit test on object set. </summary> internal static void Main() { Stopwatch sw = new Stopwatch(); sw.Start(); ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable(); for (int i = 0; i < 10000000; ++i) { object obj = new object(); if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); } if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } public bool IsExist(object obj) { return objectSet.TryGetValue(obj, out tryGetValue_out0); } public bool Add(object obj) { if (IsExist(obj)) { return false; } else { objectSet.Add(obj, null); return true; } } /// <summary> internal representation of the set. (only use the key) </summary> private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>(); /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary> private static object tryGetValue_out0 = null; } [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")] public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet { /// <summary> unit test on object set. </summary> internal static void Main() { Stopwatch sw = new Stopwatch(); sw.Start(); ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator(); for (int i = 0; i < 10000000; ++i) { object obj = new object(); if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); } if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); } } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } public bool IsExist(object obj) { bool firstTime; idGenerator.HasId(obj, out firstTime); return !firstTime; } public bool Add(object obj) { bool firstTime; idGenerator.GetId(obj, out firstTime); return firstTime; } /// <summary> internal representation of the set. </summary> private ObjectIDGenerator idGenerator = new ObjectIDGenerator(); } } 

在我的testing中, ObjectIDGenerator将抛出一个exception来抱怨在for循环中创build10,000,000个对象(比上面的代码中的10倍多)时有太多的对象。

此外,基准testing结果是, ConditionalWeakTable实现比ObjectIDGenerator实现快1.8倍。