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

我正在编写一个调试插件,我需要获得某种类型的 ID 作为引用,这在程序运行期间是唯一的。

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

堆栈溢出问题 对象的默认实现。 GetHashCode () 可能与此有关。

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

我需要构建散列表 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
}
142656 次浏览

引用 abc 0物体的唯一标识符。我不知道有什么方法可以把它转换成字符串之类的东西。引用的值在压缩过程中会发生变化(如您所见) ,但是之前的每个值 A 都会变为值 B,因此就安全代码而言,它仍然是一个唯一的 ID。

如果所涉及的对象在您的控制之下,您可以使用 参考文献不足(以避免垃圾回收)从引用到您选择的 ID (GUID、整数,等等)创建映射。然而,这会增加一定的开销和复杂性。

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

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

对于与数据库相关的记录,主键可能很有用(但仍然可以获得重复的记录)。或者,要么使用 Guid,要么保留您自己的计数器,使用 Interlocked.Increment进行分配(并使其足够大,以免溢出)。

检查了 ObjectIDGenerator类? 这就是你正在尝试做的,也是 Marc Gravell 所描述的。

ObjectIDGenerator 跟踪先前标识的对象。当您请求一个对象的 ID 时,ObjectIDGenerator 知道是返回现有的 ID,还是生成并记住一个新的 ID。

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

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

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

你可以在一秒钟内发展自己的事业,例如:

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

您可以自己选择希望具有的唯一 ID,例如 System。伙计。NewGuid ()或者简单的整数,以获得最快的访问速度。

我知道这个问题已经得到了回答,但是至少注意到你可以使用:

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

它不会直接提供“惟一 id”,而是与 WeakReferences (以及散列?)组合在一起可以为您提供一种非常简单的方法来跟踪各种实例。

在 Visual Studio 中可以创建唯一的对象标识符: 在 watch 窗口中,右键单击 object 变量并从上下文菜单中选择 制作目标 ID

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

这个方法怎么样:

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

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

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

有问题吗?

.NET 4及以后版本

好消息,各位!

这项工作的完美工具是内置在.NET 4,它被称为 ConditionalWeakTable<TKey, TValue>。这个类:

  • 可以用来将任意数据与托管对象实例关联,就像字典一样(尽管它是 而不是字典)
  • 不依赖于内存地址,因此不受 GC 压缩堆的影响
  • 不会仅仅因为对象作为键输入到表中而使其保持活动状态,所以可以使用它,而无需使流程中的每个对象永久活动
  • 使用引用相等性来确定对象标识; 此外,类作者不能修改此行为,因此可以对任何类型的对象使用 始终如一
  • 可以动态填充,因此不需要在对象构造函数中注入代码

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

这个代码的想法很简单:

  • 对象需要一个唯一的 ID,这在默认情况下是不存在的。相反,我们必须依靠下一个最好的东西,即 RuntimeHelpers.GetHashCode来获得某种唯一的 ID
  • 为了检查惟一性,这意味着我们需要使用 object.ReferenceEquals
  • 但是,我们仍然希望有一个唯一的 ID,所以我添加了一个 GUID,它的定义是唯一的。
  • 因为我不喜欢在没有必要的时候锁所有东西,所以我不用 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;
}
}

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


附录

所以,这里还有更多的内容; ,让我写一些关于 ConditionalWeakTable的内容。

ConditionalWeakTable有几个功能。最重要的是,它不关心垃圾收集器,即: 无论如何都会收集您在此表中引用的对象。如果查找一个对象,它的工作原理基本上与上面的字典相同。

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

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

因此,现在有两个问题需要解决。首先,对象可以在堆上移动,那么我们将使用什么作为 IntPtr?其次,我们如何知道对象有一个活动引用?

  • 对象可以固定在堆上,并且可以存储它的实际指针。当 GC 命中要删除的对象时,它会解开该对象并收集它。但是,这将意味着我们得到一个固定的资源,如果您有很多对象(由于内存碎片问题) ,这不是一个好主意。事情可能不是这样的。
  • 当 GC 移动一个对象时,它会回调,然后可以更新引用。根据 DependentHandle中的外部调用判断,这可能就是它的实现方式——但我相信它要稍微复杂一些。
  • 不是指向对象本身的指针,而是存储 GC 中所有对象列表中的指针。IntPtr 是此列表中的索引或指针。列表只有在对象更改代时才会更改,此时一个简单的回调就可以更新指针。如果你还记得 Mark & Sweep 是怎么运作的,这就说得通了。没有固定,移除和以前一样。我相信这就是 DependentHandle的工作原理。

最后一个解决方案确实要求运行时在显式释放列表桶之前不重用它们,并且还要求通过对运行时的调用检索所有对象。

如果我们假设他们使用这个解决方案,我们也可以解决第二个问题。标记与扫描算法跟踪已经收集的对象; 只要它已经被收集,我们就知道在这一点上。一旦对象检查对象是否存在,它就调用“ Free”,从而删除指针和列表条目。那个东西真的不见了。

此时需要注意的一点是,如果 ConditionalWeakTable在多个线程中更新,并且它不是线程安全的,那么就会出现严重错误。结果会导致内存泄漏。这就是为什么 ConditionalWeakTable中的所有调用都会做一个简单的“锁”,以确保这种情况不会发生。

另一件需要注意的事情是,必须偶尔清理条目。虽然实际的对象将由 GC 清理,但条目不会。这就是为什么 ConditionalWeakTable只会变大。一旦它达到某个限制(由散列中的碰撞机会决定) ,就会触发一个 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()返回一个唯一标识符(见 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~) :

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

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

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

因此,这种方法有很大的局限性。

还有 甚至更多,如果您想构建一个通用库会怎么样? 您不仅不能修改所用类的源代码,而且它们的行为也是不可预测的。

我很感激 乔恩西蒙已经发布了他们的答案,我将在下面发布一个代码示例和性能建议。

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

在我的测试中,ObjectIDGenerator会抛出一个异常来抱怨在 for循环中创建10,000,000个对象(比上面的代码多10倍)时有太多的对象。

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