문제

인스턴스의 고유 식별자를 얻는 방법이 있습니까?

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를 가져와야 합니다.

나는 이미 인스턴스의 내부 주소를 가져왔습니다. 이는 가비지 수집기(GC)가 힙을 압축(= 객체 이동 = 주소 변경)할 때까지 고유합니다.

스택 오버플로 질문 Object.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
}
도움이 되었습니까?

해결책

참조 ~이다 객체의 고유 식별자. 나는 이것을 문자열 등으로 변환하는 방법을 모른다. 안전한 코드가 관련되어 있기 때문에 여전히 고유 한 ID입니다.

관련된 객체가 귀하의 제어하에있는 경우 약한 참조 (쓰레기 수집을 방지하지 않으려면) 선택한 ID (Guid, Integer 등)를 참조하십시오. 그러나 일정량의 오버 헤드와 복잡성이 추가됩니다.

다른 팁

.NET 4 이상에만 해당

좋은 소식입니다, 여러분!

이 작업을 위한 완벽한 도구는 .NET 4에 내장되어 있으며 이를 호출합니다. ConditionalWeakTable<TKey, TValue>.이 수업은:

  • 사전과 마찬가지로 임의의 데이터를 관리 개체 인스턴스와 연결하는 데 사용할 수 있습니다. ~이다 사전이 아닙니다)
  • 메모리 주소에 의존하지 않으므로 힙을 압축하는 GC에 영향을 받지 않습니다.
  • 테이블에 키로 입력되었다는 이유만으로 객체를 활성 상태로 유지하지 않으므로 프로세스의 모든 객체를 영원히 유지하지 않고도 사용할 수 있습니다.
  • 객체 동일성을 결정하기 위해 참조 동일성을 사용합니다.moveover, 클래스 작성자는 이 동작을 수정하여 사용할 수 없습니다. 꾸준히 모든 유형의 객체에
  • 즉석에서 채울 수 있으므로 객체 생성자 내부에 코드를 삽입할 필요가 없습니다.

체크 아웃 ObjectIdgenerator 수업? 이것은 당신이 시도하는 일과 Marc Gravell이 묘사 한 것입니다.

ObjectIdGenerator는 이전에 식별 된 객체를 추적합니다. 객체의 ID를 요청할 때 ObjectIdGenerator는 기존 ID를 반환 할 것인지 또는 새 ID를 생성하고 기억하는지 여부를 알고 있습니다.

ID는 ObjectIdGenerator 인스턴스의 수명에 고유합니다. 일반적으로, 대상 이사기 수명은 그것을 만든 형성자만큼 오래 지속됩니다. 객체 ID는 주어진 직렬화 된 스트림 내에서만 의미를 가지며 직렬화 된 객체 그래프 내에서 다른 객체를 참조하는 객체를 추적하는 데 사용됩니다.

해시 테이블을 사용하여 ObjectIdGenerator는 어떤 객체에 어떤 ID가 할당되는지를 유지합니다. 각 객체를 고유하게 식별하는 객체 참조는 런타임 쓰레기 수집 힙의 주소입니다. 직렬화 중에 객체 참조 값이 변경 될 수 있지만 테이블이 자동으로 업데이트되므로 정보가 올바르게 업데이트됩니다.

객체 ID는 64 비트 번호입니다. 할당은 하나에서 시작되므로 0은 유효한 객체 ID가 아닙니다. 포피터는 값이 널리 참조 (Visual Basic에서는 없음) 인 객체 참조를 나타 내기 위해 0 값을 선택할 수 있습니다.

RuntimeHelpers.GetHashCode() 도움이 될 수 있습니다 (MSDN).

당신은 잠시 후에 자신의 것을 개발할 수 있습니다. 예를 들어:

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

예를 들어 System.guid.newguid () 또는 단순히 가장 빠른 액세스를 위해 고유 한 ID로 원하는 것을 선택할 수 있습니다.

이 방법은 어떻습니까 :

첫 번째 개체의 필드를 새 값으로 설정하십시오. 두 번째 객체의 동일한 필드가 동일한 값을 갖는 경우 아마도 동일한 인스턴스 일 것입니다. 그렇지 않으면 다른 종료하십시오.

이제 첫 번째 개체의 필드를 다른 새 값으로 설정하십시오. 두 번째 객체의 동일한 필드가 다른 값으로 변경되면 확실히 동일한 인스턴스입니다.

첫 번째 개체에서 필드를 출구시 원래 값으로 다시 설정하는 것을 잊지 마십시오.

문제?

Visual Studio에서 고유 한 객체 식별자를 만들 수 있습니다 : 시계 창에서 객체 변수를 마우스 오른쪽 버튼으로 클릭하고 선택하십시오. 객체 ID를 만드십시오 맥락 메뉴에서.

불행히도, 이것은 수동 단계이며, 식별자가 코드를 통해 액세스 할 수 있다고 생각하지 않습니다.

그러한 식별자를 인스턴스 내부 또는 외부 적으로 수동으로 직접 할당해야합니다.

데이터베이스와 관련된 레코드의 경우 기본 키가 유용 할 수 있습니다 (그러나 여전히 복제를 얻을 수 있습니다). 또는 a를 사용하십시오 Guid, 또는 자신의 카운터를 유지하고 사용을 할당하십시오 Interlocked.Increment (그리고 오버플로 될 가능성이 높지 않을 정도로 커집니다).

나는 이것이 답변되었음을 알고 있지만, 당신이 사용할 수 있다는 점에 주목하는 것이 적어도 유용합니다.

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

"고유 한 ID"를 직접 제공하지는 않지만 약점 (및 해시 세트)과 결합하면 다양한 인스턴스를 추적하는 매우 쉬운 방법을 제공 할 수 있습니다.

내가 여기에 제공하는 정보는 새로운 것이 아니며, 완전성을 위해 이것을 추가했습니다.

이 코드의 아이디어는 매우 간단합니다.

  • 객체에는 기본적으로 존재하지 않는 고유 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.

이 마지막 솔루션에서는 런타임이 명시적으로 해제될 때까지 목록 버킷을 재사용하지 않아야 하며 런타임 호출을 통해 모든 개체를 검색해야 합니다.

그들이 이 솔루션을 사용한다고 가정하면 두 번째 문제도 해결할 수 있습니다.Mark & ​​Sweep 알고리즘은 어떤 객체가 수집되었는지 추적합니다.수집되자마자 우리는 이 시점에서 알게 됩니다.개체가 개체가 있는지 확인하면 포인터와 목록 항목을 제거하는 'Free'를 호출합니다.개체가 실제로 사라졌습니다.

이 시점에서 주목해야 할 중요한 점은 다음과 같은 경우 상황이 끔찍하게 잘못된다는 것입니다. ConditionalWeakTable 여러 스레드에서 업데이트되고 스레드로부터 안전하지 않은 경우 업데이트됩니다.결과적으로 메모리 누수가 발생합니다.이것이 모든 전화가 들어오는 이유입니다. ConditionalWeakTable 이런 일이 발생하지 않도록 간단한 '잠금'을 수행하십시오.

또 한 가지 주목해야 할 점은 항목 정리가 가끔 발생해야 한다는 것입니다.실제 개체는 GC에 의해 정리되지만 항목은 그렇지 않습니다.이는 이유 ConditionalWeakTable 크기만 커집니다.특정 한도(해시 충돌 가능성에 따라 결정됨)에 도달하면 Resize, 개체를 정리해야 하는지 확인합니다. 정리가 필요한 경우 free GC 프로세스에서 호출되어 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() 고유 식별자를 반환합니다 (참조 Object.gethashCode Method ()):

동일한 해시 코드가 객체 평등을 암시한다고 가정해서는 안됩니다.

, 당신은 아주 적은 양의 물체가 있다고 가정합니다. GetHashCode() 대부분의 경우 작동 할 경우이 방법은 일부 유형으로 재정의 할 수 있습니다.
예를 들어, 당신은 일부 클래스 C를 사용하고 있고 그것은 무시합니다. GetHashCode() 항상 0을 반환하려면 C의 모든 객체는 동일한 해시 코드를 얻습니다. 안타깝게도, Dictionary, HashTable 그리고 다른 연관 컨테이너는이 방법을 사용합니다.

해시 코드는 사전과 같은 해시 기반 컬렉션에서 객체를 삽입하고 식별하는 데 사용되는 숫자 값입니다.u003CTKey, TValue> 클래스, 해시 가능 클래스 또는 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 10,000,000 개 객체 (위의 코드보다 10 배)를 만들 때 너무 많은 객체가 있다고 불평하는 예외가 발생합니다. for 고리.

또한 벤치 마크 결과는 다음과 같습니다 ConditionalWeakTable 구현은 ObjectIDGenerator 구현.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top