C# .NET 唯一对象标识符
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/750947/
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
.NET unique object identifier
提问by Martin Konicek
Is there a way of getting a unique identifier of an instance?
有没有办法获取实例的唯一标识符?
GetHashCode()
is the same for the two references pointing to the same instance. However, two different instances can (quite easily) get the same hash code:
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);
}
I'm writing a debugging addin, and I need to get some kind of ID for a reference which is unique during the run of the program.
我正在编写一个调试插件,我需要获取某种 ID 以供参考,该 ID 在程序运行期间是唯一的。
I already managed to get internal ADDRESS of the instance, which is unique until the garbage collector (GC) compacts the heap (= moves the objects = changes the addresses).
我已经设法获得了实例的内部地址,这在垃圾收集器 (GC) 压缩堆之前是唯一的(= 移动对象 = 更改地址)。
Stack Overflow question Default implementation for Object.GetHashCode()might be related.
堆栈溢出问题Object.GetHashCode() 的默认实现可能是相关的。
The objects are not under my control as I am accessing objects in a program being debugged using the debugger API. If I was in control of the objects, adding my own unique identifiers would be trivial.
这些对象不在我的控制之下,因为我正在使用调试器 API 访问正在调试的程序中的对象。如果我可以控制对象,添加我自己的唯一标识符将是微不足道的。
I wanted the unique ID for building a hashtable ID -> object, to be able to lookup already seen objects. For now I solved it like this:
我想要用于构建哈希表 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
}
采纳答案by Jon Skeet
The reference isthe unique identifier for the object. I don't know of any way of converting this into anything like a string etc. The value of the reference will change during compaction (as you've seen), but every previous value A will be changed to value B, so as far as safe code is concerned it's still a unique ID.
引用是对象的唯一标识符。我不知道有什么方法可以将其转换为字符串等。引用的值会在压缩过程中发生变化(如您所见),但是之前的每个值 A 都将更改为值 B,到目前为止就安全代码而言,它仍然是一个唯一的 ID。
If the objects involved are under your control, you could create a mapping using weak references(to avoid preventing garbage collection) from a reference to an ID of your choosing (GUID, integer, whatever). That would add a certain amount of overhead and complexity, however.
如果所涉及的对象在您的控制之下,您可以使用弱引用(以避免防止垃圾回收)从对您选择的 ID(GUID、整数等)的引用创建映射。然而,这会增加一定的开销和复杂性。
回答by Anton Gogolev
回答by Marc Gravell
You would have to assign such an identifier yourself, manually - either inside the instance, or externally.
您必须自己手动分配这样的标识符 - 在实例内部或外部。
For records related to a database, the primary key may be useful (but you can still get duplicates). Alternatively, either use a Guid
, or keep your own counter, allocating using Interlocked.Increment
(and make it large enough that it isn't likely to overflow).
对于与数据库相关的记录,主键可能很有用(但您仍然可以获得重复项)。或者,要么使用 a Guid
,要么保留自己的计数器,分配 using Interlocked.Increment
(并使其足够大以防止溢出)。
回答by sisve
Checked out the ObjectIDGeneratorclass? This does what you're attempting to do, and what Marc Gravell describes.
是否检查了ObjectIDGenerator类?这完成了您正在尝试做的事情,以及 Marc Gravell 所描述的内容。
The ObjectIDGenerator keeps track of previously identified objects. When you ask for the ID of an object, the ObjectIDGenerator knows whether to return the existing ID, or generate and remember a new ID.
The IDs are unique for the life of the ObjectIDGenerator instance. Generally, a ObjectIDGenerator life lasts as long as the Formatter that created it. Object IDs have meaning only within a given serialized stream, and are used for tracking which objects have references to others within the serialized object graph.
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 a null reference (Nothing in Visual Basic).
ObjectIDGenerator 跟踪先前识别的对象。当您询问对象的 ID 时,ObjectIDGenerator 知道是返回现有 ID,还是生成并记住新 ID。
这些 ID 在 ObjectIDGenerator 实例的生命周期内是唯一的。通常,ObjectIDGenerator 的生命周期与创建它的格式化程序一样长。对象 ID 仅在给定的序列化流中有意义,用于跟踪哪些对象引用了序列化对象图中的其他对象。
使用哈希表,ObjectIDGenerator 保留分配给哪个对象的 ID。唯一标识每个对象的对象引用是运行时垃圾收集堆中的地址。对象引用值可以在序列化期间更改,但表会自动更新,因此信息是正确的。
对象 ID 是 64 位数字。分配从 1 开始,因此 0 永远不是有效的对象 ID。格式化程序可以选择零值来表示其值为空引用(在 Visual Basic 中为 Nothing)的对象引用。
回答by majkinetor
You can develop your own thing in a second. For instance:
您可以在一秒钟内开发自己的东西。例如:
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++;
}
}
You can choose what you will like to have as unique ID on your own, for instance, System.Guid.NewGuid() or simply integer for fastest access.
您可以自行选择您希望拥有的唯一 ID,例如 System.Guid.NewGuid() 或简单的整数以实现最快访问。
回答by Andrew Theken
I know that this has been answered, but it's at least useful to note that you can use:
我知道这已经得到了回答,但至少要注意您可以使用:
http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx
http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx
Which will not give you a "unique id" directly, but combined with WeakReferences (and a hashset?) could give you a pretty easy way of tracking various instances.
这不会直接为您提供“唯一 ID”,但与 WeakReferences(和哈希集?)相结合,可以为您提供一种非常简单的跟踪各种实例的方法。
回答by Thomas Bratt
It is possible to make a unique object identifier in Visual Studio: In the watch window, right-click the object variable and choose Make Object IDfrom the context menu.
可以在 Visual Studio 中创建唯一的对象标识符:在监视窗口中,右键单击对象变量并从上下文菜单中选择“创建对象 ID”。
Unfortunately, this is a manual step, and I don't believe the identifier can be accessed via code.
不幸的是,这是一个手动步骤,我不相信可以通过代码访问标识符。
回答by Dawg
How about this method:
这个方法怎么样:
Set a field in the first object to a new value. If the same field in the second object has the same value, it's probably the same instance. Otherwise, exit as different.
将第一个对象中的字段设置为新值。如果第二个对象中的相同字段具有相同的值,则它可能是相同的实例。否则,以不同的方式退出。
Now set the field in the first object to a different new value. If the same field in the second object has changed to the different value, it's definitely the same instance.
现在将第一个对象中的字段设置为不同的新值。如果第二个对象中的相同字段已更改为不同的值,则肯定是同一个实例。
Don't forget to set field in the first object back to it's original value on exit.
不要忘记在退出时将第一个对象中的字段设置回其原始值。
Problems?
问题?
回答by Jon
.NET 4 and later only
仅适用于 .NET 4 及更高版本
Good news, everyone!
好消息,大家!
The perfect tool for this job is built in .NET 4 and it's called ConditionalWeakTable<TKey, TValue>
. This class:
这项工作的完美工具内置于 .NET 4 中,它被称为ConditionalWeakTable<TKey, TValue>
. 这节课:
- can be used to associate arbitrary data with managed object instances much like a dictionary (although it isnot a dictionary)
- does not depend on memory addresses, so is immune to the GC compacting the heap
- does not keep objects alive just because they have been entered as keys into the table, so it can be used without making every object in your process live forever
- uses reference equality to determine object identity; moveover, class authors cannot modify this behavior so it can be used consistentlyon objects of any type
- can be populated on the fly, so does not require that you inject code inside object constructors
- 可用于很像一个字典与被管理对象实例的任意数据相关联(尽管它是不是一个字典)
- 不依赖于内存地址,因此不受 GC 压缩堆的影响
- 不会仅仅因为对象已作为键输入到表中而使对象保持活动状态,因此可以使用它而无需使进程中的每个对象永远存在
- 使用引用相等来确定对象身份;移动,类作者无法修改此行为,因此它可以在任何类型的对象上一致使用
- 可以即时填充,因此不需要您在对象构造函数中注入代码
回答by atlaste
The information I give here is not new, I just added this for completeness.
我在这里提供的信息并不新鲜,我只是为了完整性添加了这个。
The idea of this code is quite simple:
这段代码的想法很简单:
- Objects need a unique ID, which isn't there by default. Instead, we have to rely on the next best thing, which is
RuntimeHelpers.GetHashCode
to get us a sort-of unique ID - To check uniqueness, this implies we need to use
object.ReferenceEquals
- However, we would still like to have a unique ID, so I added a
GUID
, which is by definition unique. - Because I don't like locking everything if I don't have to, I don't use
ConditionalWeakTable
.
- 对象需要一个唯一的 ID,默认情况下不存在。相反,我们必须依靠下一个最好的事情,那就是
RuntimeHelpers.GetHashCode
为我们提供一种唯一的 ID - 要检查唯一性,这意味着我们需要使用
object.ReferenceEquals
- 但是,我们仍然希望有一个唯一的 ID,所以我添加了一个
GUID
,它根据定义是唯一的。 - 因为我不喜欢在不需要的情况下锁定所有内容,所以我不使用
ConditionalWeakTable
.
Combined, that will give you the following code:
结合起来,这将为您提供以下代码:
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;
}
}
To use it, create an instance of the UniqueIdMapper
and use the GUID's it returns for the objects.
要使用它,请创建 的实例UniqueIdMapper
并使用它为对象返回的 GUID。
Addendum
附录
So, there's a bit more going on here; let me write a bit down about ConditionalWeakTable
.
所以,这里还有一些事情要做;让我写一下ConditionalWeakTable
。
ConditionalWeakTable
does a couple of things. The most important thing is that it doens't care about the garbage collector, that is: the objects that you reference in this table will be collected regardless. If you lookup an object, it basically works the same as the dictionary above.
ConditionalWeakTable
做了几件事。最重要的是它不关心垃圾收集器,即:不管你在这个表中引用的对象都会被收集。如果您查找一个对象,它的工作原理与上面的字典基本相同。
Curious no? After all, when an object is being collected by the GC, it checks if there are references to the object, and if there are, it collects them. So if there's an object from the ConditionalWeakTable
, why will the referenced object be collected then?
好奇不?毕竟,当一个对象被 GC 收集时,它会检查是否有对该对象的引用,如果有,它就会收集它们。那么如果有一个来自 的对象,那么ConditionalWeakTable
为什么要收集引用的对象呢?
ConditionalWeakTable
uses a small trick, which some other .NET structures also use: instead of storing a reference to the object, it actually stores an IntPtr. Because that's not a real reference, the object can be collected.
ConditionalWeakTable
使用了一个小技巧,其他一些 .NET 结构也使用了这个技巧:它实际上存储了一个 IntPtr,而不是存储对对象的引用。因为那不是真正的引用,所以可以收集对象。
So, at this point there are 2 problems to address. First, objects can be moved on the heap, so what will we use as IntPtr? And second, how do we know that objects have an active reference?
所以,在这一点上有两个问题需要解决。首先,对象可以在堆上移动,那么我们将使用什么作为 IntPtr 呢?其次,我们怎么知道对象有一个活动引用?
- The object can be pinned on the heap, and its real pointer can be stored. When the GC hits the object for removal, it unpins it and collects it. However, that would mean we get a pinned resource, which isn't a good idea if you have a lot of objects (due to memory fragmentation issues). This is probably not how it works.
- When the GC moves an object, it calls back, which can then update the references. This might be how it's implemented judging by the external calls in
DependentHandle
- but I believe it's slightly more sophisticated. - Not the pointer to the object itself, but a pointer in the list of all objects from the GC is stored. The IntPtr is either an index or a pointer in this list. The list only changes when an object changes generations, at which point a simple callback can update the pointers. If you remember how Mark & Sweep works, this makes more sense. There's no pinning, and removal is as it was before. I believe this is how it works in
DependentHandle
.
- 对象可以固定在堆上,并且可以存储它的真实指针。当 GC 命中要移除的对象时,它会取消固定并收集它。但是,这意味着我们会得到一个固定资源,如果您有很多对象(由于内存碎片问题),这不是一个好主意。这可能不是它的工作方式。
- 当 GC 移动一个对象时,它会回调,然后可以更新引用。从外部调用来看,这可能是它的实现方式
DependentHandle
- 但我相信它稍微复杂一些。 - 不是指向对象本身的指针,而是存储来自 GC 的所有对象列表中的指针。IntPtr 是此列表中的索引或指针。列表仅在对象更改世代时更改,此时简单的回调可以更新指针。如果您还记得 Mark & Sweep 的工作原理,这会更有意义。没有固定,拆卸和以前一样。我相信这就是它在
DependentHandle
.
This last solution does require that the runtime doesn't re-use the list buckets until they are explicitly freed, and it also requires that all objects are retrieved by a call to the runtime.
最后一个解决方案确实要求运行时在显式释放列表存储桶之前不会重新使用它们,并且它还要求通过调用运行时来检索所有对象。
If we assume they use this solution, we can also address the second problem. The Mark & Sweep algorithm keeps track of which objects have been collected; as soon as it has been collected, we know at this point. Once the object checks if the object is there, it calls 'Free', which removes the pointer and the list entry. The object is really gone.
如果我们假设他们使用这个解决方案,我们也可以解决第二个问题。Mark & Sweep 算法会跟踪哪些对象已被收集;一经收集,我们就知道了。一旦对象检查对象是否存在,它就会调用“Free”,这将删除指针和列表条目。对象真的没了。
One important thing to note at this point is that things go horribly wrong if ConditionalWeakTable
is updated in multiple threads and if it isn't thread safe. The result would be a memory leak. This is why all calls in ConditionalWeakTable
do a simple 'lock' which ensures this doesn't happen.
在这一点上要注意的一件重要事情是,如果ConditionalWeakTable
在多个线程中更新并且它不是线程安全的,那么事情会变得非常错误。结果将是内存泄漏。这就是为什么所有调用都ConditionalWeakTable
执行简单的“锁定”以确保不会发生这种情况。
Another thing to note is that cleaning up entries has to happen once in a while. While the actual objects will be cleaned up by the GC, the entries are not. This is why ConditionalWeakTable
only grows in size. Once it hits a certain limit (determined by collision chance in the hash), it triggers a Resize
, which checks if objects have to be cleaned up -- if they do, free
is called in the GC process, removing the IntPtr
handle.
另一件要注意的事情是清理条目必须不时发生。虽然实际对象将由 GC 清理,但条目不会。这就是为什么ConditionalWeakTable
只在规模上增长。一旦它达到某个限制(由散列中的碰撞机会确定),它就会触发 a Resize
,它检查对象是否必须被清理——如果必须清理,free
则在 GC 过程中调用,删除IntPtr
句柄。
I believe this is also why DependentHandle
is not exposed directly - you don't want to mess with things and get a memory leak as a result. The next best thing for that is a WeakReference
(which also stores an IntPtr
instead of an object) - but unfortunately doesn't include the 'dependency' aspect.
我相信这也是为什么DependentHandle
不直接暴露的原因- 你不想弄乱事情并因此导致内存泄漏。下一个最好的事情是 a WeakReference
(它也存储一个IntPtr
而不是一个对象) - 但不幸的是不包括“依赖”方面。
What remains is for you to toy around with the mechanics, so that you can see the dependency in action. Be sure to start it multiple times and watch the results:
剩下的就是让您玩弄机制,以便您可以看到依赖关系。一定要多次启动并观察结果:
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
}