.NET唯一对象标识符

时间:2009-04-15 09:39:40

标签: c# unique hashcode gethashcode

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

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
}

11 个答案:

答案 0 :(得分:65)

仅限.NET 4及更高版本

好消息,大家好!

这项工作的完美工具是在.NET 4中构建的,它被称为ConditionalWeakTable<TKey, TValue>。这堂课:

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

答案 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)

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

这段代码的想法非常简单:

  • 对象需要一个唯一的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&amp;扫描工作,这更有意义。没有固定,移除就像以前一样。我相信这是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的每个对象都将获得相同的哈希代码。 不幸的是,DictionaryHashTable和其他一些关联容器将使用此方法:

  

哈希码是一个数值,用于插入和标识基于散列的集合中的对象,例如Dictionary&lt; TKey,TValue&gt; class,Hashtable类或从DictionaryBase类派生的类型。 GetHashCode方法为需要快速检查对象相等性的算法提供此哈希代码。

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

更多,如果您想构建通用库,该怎么办? 您不仅无法修改已使用类的源代码,而且其行为也无法预测。

我感谢JonSimon发布了他们的答案,我将在下面发布一个代码示例和一个关于性能的建议。

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倍。