有没有办法获取实例的唯一标识符?
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)压缩堆(=移动对象=更改地址)。
Stack Overflow问题 Default implementation for Object.GetHashCode() 可能是相关的。
由于我正在使用调试器API调试程序中的对象,因此对象不受我的控制。如果我控制对象,添加我自己的唯一标识符将是微不足道的。
我想要用于构建哈希表ID的唯一ID - &gt;对象,能够查找已经看过的对象。现在我解决了这个问题:
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
}
答案 0 :(得分:65)
好消息,大家好!
这项工作的完美工具是在.NET 4中构建的,它被称为ConditionalWeakTable<TKey, TValue>
。这堂课:
答案 1 :(得分:39)
引用是对象的唯一标识符。我不知道有什么方法可以将它转换成类似字符串等的东西。参考的值会在压缩过程中发生变化(正如你所见),但是每个先前的值A都会变为值B,所以到目前为止就安全代码而言,它仍然是一个独特的ID。
如果涉及的对象在您的控制之下,您可以使用weak references创建映射(以避免垃圾回收)来引用您选择的ID(GUID,整数,等等)。但是,这会增加一定的开销和复杂性。
答案 2 :(得分:39)
检查了ObjectIDGenerator课程?这就是你要做的,以及Marc Gravell所描述的内容。
ObjectIDGenerator跟踪先前识别的对象。当您要求提供对象的ID时,ObjectIDGenerator会知道是返回现有ID,还是生成并记住新ID。
ID在ObjectIDGenerator实例的生命周期中是唯一的。通常,ObjectIDGenerator的生命周期与创建它的Formatter一样长。对象ID仅在给定的序列化流中具有意义,并用于跟踪哪些对象在序列化对象图中引用了其他对象。
使用哈希表,ObjectIDGenerator会保留为哪个对象分配的ID。唯一标识每个对象的对象引用是运行时垃圾收集堆中的地址。对象引用值可以在序列化期间更改,但表会自动更新,以便信息正确。
对象ID是64位数字。分配从一开始,因此零永远不是有效的对象ID。格式化程序可以选择零值来表示其值为空引用的对象引用(在Visual Basic中为Nothing)。
答案 3 :(得分:34)
RuntimeHelpers.GetHashCode()
可能有所帮助(MSDN)。
答案 4 :(得分:7)
你可以在一秒钟内发展自己的东西。例如:
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.Guid.NewGuid(),或者只是整数以便快速访问。
答案 5 :(得分:6)
这个方法怎么样:
将第一个对象中的字段设置为新值。如果第二个对象中的相同字段具有相同的值,则它可能是相同的实例。否则,退出不同。
现在将第一个对象中的字段设置为不同的新值。如果第二个对象中的相同字段已更改为不同的值,则它肯定是相同的实例。
不要忘记在退出时将第一个对象中的字段设置回原始值。
问题?
答案 6 :(得分:4)
可以在Visual Studio中创建唯一的对象标识符:在监视窗口中,右键单击对象变量,然后从上下文菜单中选择生成对象ID 。
不幸的是,这是一个手动步骤,我不相信可以通过代码访问标识符。
答案 7 :(得分:3)
您必须自己手动分配这样的标识符 - 在实例内部或外部。
对于与数据库相关的记录,主键可能很有用(但您仍然可以获得重复项)。或者,要么使用Guid
,要么保留自己的计数器,使用Interlocked.Increment
进行分配(并使其足够大以至于不会溢出)。
答案 8 :(得分:2)
我知道这已经得到了解答,但至少有用的是要注意你可以使用:
http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx
哪个不会直接给你一个“唯一ID”,但结合WeakReferences(以及一个hashset?)可以为你提供一种非常简单的跟踪各种实例的方法。
答案 9 :(得分:1)
我在这里提供的信息并不新鲜,我只是为了完整而添加了这些信息。
这段代码的想法非常简单:
RuntimeHelpers.GetHashCode
来获取一种独特的ID object.ReferenceEquals
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?第二,我们怎么知道对象有一个有效的参考?
DependentHandle
中通过外部调用判断的方式 - 但我相信它稍微复杂一些。DependentHandle
。最后一个解决方案确实要求运行时不会重复使用列表桶,直到它们被显式释放,并且还要求通过调用运行时来检索所有对象。
如果我们假设他们使用此解决方案,我们也可以解决第二个问题。马克&amp;扫描算法跟踪已收集的对象;一旦收集完毕,我们就知道了。一旦对象检查对象是否存在,它就会调用“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
}
答案 10 :(得分:0)
如果您在自己的代码中为特定用途编写模块,majkinetor's method 可能已经有效。但是有一些问题。
首先,官方文档不保证GetHashCode()
返回唯一标识符(请参阅 Object.GetHashCode Method () ):
您不应该假设相等的哈希码意味着对象相等。
第二,假设您拥有非常少量的对象,以便GetHashCode()
在大多数情况下都可以使用,此方法可以被某些类型覆盖。
例如,您正在使用某个类C并且它会覆盖GetHashCode()
以始终返回0.然后,C的每个对象都将获得相同的哈希代码。
不幸的是,Dictionary
,HashTable
和其他一些关联容器将使用此方法:
哈希码是一个数值,用于插入和标识基于散列的集合中的对象,例如Dictionary&lt; TKey,TValue&gt; class,Hashtable类或从DictionaryBase类派生的类型。 GetHashCode方法为需要快速检查对象相等性的算法提供此哈希代码。
所以,这种方法有很大的局限性。
更多,如果您想构建通用库,该怎么办? 您不仅无法修改已使用类的源代码,而且其行为也无法预测。
我感谢Jon和Simon发布了他们的答案,我将在下面发布一个代码示例和一个关于性能的建议。
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倍。