代码之家  ›  专栏  ›  技术社区  ›  Martin Konicek Phil Windley

.NET唯一对象标识符

  •  104
  • Martin Konicek Phil Windley  · 技术社区  · 15 年前

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

    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,该ID在程序运行期间是唯一的。

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

    堆栈溢出问题 Default implementation for Object.GetHashCode() 可能有关联。

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

    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 回复  |  直到 7 年前
        1
  •  76
  •   Uwe Keim Chand    6 年前

    仅限.NET 4及更高版本

    各位,好消息!

    用于此工作的完美工具是.NET4中内置的,它被称为 ConditionalWeakTable<TKey, TValue> . 这一类:

    • 可用于将任意数据与托管对象实例关联,就像字典一样(尽管 (不是字典)
    • 不能仅仅因为对象已作为键输入到表中而使其保持活动状态,因此可以使用它,而不会使流程中的每个对象永远活动
    • 使用引用等式确定对象标识;moveover,类作者无法修改此行为以便可以使用它 在任何类型的对象上
    • 可以动态填充,因此不需要在对象构造函数中插入代码
        2
  •  46
  •   Community Ian Goodfellow    4 年前

    查看 ObjectIDGenerator

    ObjectedGenerator跟踪以前识别的对象。当您请求对象的ID时,ObjectedGenerator知道是返回现有ID,还是生成并记住新ID。

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

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

        3
  •  45
  •   Jon Skeet    15 年前

    参考文献

    weak references (以避免阻止垃圾回收)从对您选择的ID(GUID、整数等)的引用中删除。然而,这将增加一定的开销和复杂性。

        4
  •  39
  •   larsmoa    11 年前

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

        5
  •  7
  •   majkinetor    15 年前

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

       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()或integer,以实现最快的访问。

        6
  •  7
  •   Dawg    13 年前

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

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

    问题?

        7
  •  5
  •   Peter Mortensen rohan kamat    7 年前

    从上下文菜单中。

        8
  •  4
  •   Marc Gravell    15 年前

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

    对于与数据库相关的记录,主键可能很有用(但仍然可以获得重复项)。或者,使用 Guid ,或保留自己的计数器,使用 Interlocked.Increment

        9
  •  2
  •   Andrew Theken Tan Li Hau    14 年前

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

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

        10
  •  1
  •   atlaste    10 年前

    我在这里提供的信息不是新的,我只是为了完整性添加了这个。

    此代码的思想非常简单:

    • 对象需要唯一的ID,默认情况下不存在该ID。相反,我们必须依靠下一个最好的东西,那就是 RuntimeHelpers.GetHashCode
    • 要检查唯一性,这意味着我们需要使用 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。


    补遗

    所以,这里还有更多的事情要做;让我写一点关于 .

    条件脆弱的 做了几件事。最重要的是,它不关心垃圾收集器,也就是说:您在此表中引用的对象将被收集。如果您查找一个对象,它的工作原理基本上与上面的字典相同。

    好奇的是不是?毕竟,当GC收集对象时,它会检查是否有对该对象的引用,如果有,它会收集它们。所以如果有一个物体从 条件脆弱的 ,那么为什么要收集引用的对象?

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

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

    • 当GC移动一个对象时,它会回调,然后可以更新引用。从中的外部调用判断,这可能就是它的实现方式 DependentHandle
    • 不是指向对象本身的指针,而是存储来自GC的所有对象列表中的指针。IntPtr是此列表中的索引或指针。该列表仅在对象更改生成时更改,此时简单回调可以更新指针。如果你还记得Mark&清扫有效,这更有意义。没有钉住,移除和以前一样。我相信这就是它的工作原理 依附 .

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

    如果我们假设他们使用这个解决方案,我们还可以解决第二个问题。马克,;扫描算法跟踪已收集的对象;一旦收集到,我们就知道了。一旦对象检查对象是否存在,它将调用“Free”,这将删除指针和列表项。物体真的不见了。

    在这一点上需要注意的一件重要事情是,如果 条件脆弱的 条件脆弱的

    另一件需要注意的事情是,清理条目必须偶尔进行一次。虽然GC将清理实际对象,但不会清理条目。这就是为什么 条件脆弱的 只会变大。一旦它达到某个限制(由散列中的冲突概率决定),它就会触发 Resize ,检查对象是否需要清理,如果需要, free 在GC进程中调用,删除 IntPtr 手柄

    我相信这也是原因 依附 WeakReference

    剩下的是让你玩弄这些机制,这样你就可以看到实际的依赖性了。确保多次启动并观察结果:

    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
        }
    
        11
  •  1
  •   Community Ian Goodfellow    7 年前

    如果您正在用自己的代码编写一个模块以用于特定用途, majkinetor's method 可以 我已经成功了。但也存在一些问题。

    GetHashCode() 返回唯一标识符(请参见 Object.GetHashCode Method () ):

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

    ,假设您有非常少量的对象 GetHashCode()
    例如,您正在使用某些C类,它将覆盖 GetHashCode() 不幸地 Dictionary HashTable 其他一些关联容器将使用此方法:

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

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

    我很感激 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 将引发异常,以抱怨在中创建10000000个对象时对象太多(比上述代码中的对象多10倍) for

    此外,基准结果是 ConditionalWeakTable 目标发生器 实施