.NET 고유 개체 식별자


118

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

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
}

답변:


42

참조 개체의 고유 식별자입니다. 나는 이것을 문자열 등과 같은 것으로 변환하는 방법을 모릅니다. 참조 값은 압축 중에 변경되지만 (당신이 본 것처럼) 모든 이전 값 A는 지금까지 값 B로 변경됩니다. 안전한 코드와 관련하여 여전히 고유 ID입니다.

관련된 개체가 제어하에있는 경우 선택한 ID (GUID, 정수 등)에 대한 참조에서 약한 참조 (가비지 수집 방지를 방지하기 위해)를 사용하여 매핑을 만들 수 있습니다 . 그러나 이는 일정량의 오버 헤드와 복잡성을 추가합니다.


1
추적하는 모든 참조에 대해 반복해야하는 조회를 생각합니다. 동일한 객체에 대한 WeakReference는 서로 같지 않으므로 다른 작업을 많이 할 수 없습니다.
Roman Starkov

1
특히 이러한 ID가 순차적으로 발급 된 경우 각 개체에 고유 한 64 비트 ID를 할당하면 유용 할 수 있습니다. 유용성이 비용을 정당화 할 수 있을지 확신 할 수 없지만, 두 개의 별개의 불변 객체를 비교하여 동일하다고 판단하면 그러한 것이 도움이 될 수 있습니다. 가능한 경우 하나가 이전 참조로 최신 참조를 덮어 쓰면 동일하지만 별개의 객체에 대한 중복 참조를 많이 피할 수 있습니다.
supercat

1
"식별자." 나는 그 단어가 당신이 의미한다고 생각하는 것을 의미한다고 생각하지 않습니다.
Slipp D. Thompson 2014

5
@ SlippD.Thompson : 아니요, 여전히 일대일 관계입니다. 주어진 객체를 참조하는 참조 값은 하나뿐입니다. 이 값은 메모리에 여러 번 나타날 수 있지만 (예 : 여러 변수의 값으로) 여전히 단일 값입니다. 집 주소와 같습니다. 여러 종이에 집 주소를 여러 장 적을 수 있지만 여전히 집의 식별자입니다. 두 개의 동일하지 않은 참조 값 적어도 C #에서 서로 다른 개체를 참조 해야합니다 .
Jon Skeet 2014 년

1
@supercat : "신원이 캡슐화되는 것"에 대한 우리의 이해가 다를 수 있다고 생각합니다.하지만 우리가 이미 가지고있는 것보다 더 나아갈 수 있도록 누구에게도 도움이되지 않을 것 같습니다. 우리는 이제까지 ... 사람에서의 만남
존 소총

72

.NET 4 이상 만

모두들 좋은 소식입니다!

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

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

5
완전성을 위해 : 내부 작업 ConditionalWeakTable에 의존 RuntimeHelpers.GetHashCode하고 object.ReferenceEquals수행합니다. 동작은 IEqualityComparer<T>이 두 가지 방법을 사용하는를 빌드하는 것과 동일 합니다. 성능이 필요한 경우 ConditionalWeakTable스레드를 안전하게 만들기 위해 모든 작업에 잠금 이 있으므로 실제로이 작업을 수행하는 것이 좋습니다 .
atlaste 2014 년

1
@StefandeBruijn : A ConditionalWeakTable는 각각에 대한 참조를 보유하며 Value이는 해당 Key. ConditionalWeakTable우주 어디에서나 존재하는 유일한 참조가 있는 객체 는 키가있을 때 자동으로 존재하지 않습니다.
supercat

41

체크 아웃 ObjectIDGenerator의 클래스? 이것은 당신이하려는 일과 Marc Gravell이 설명하는 것을 수행합니다.

ObjectIDGenerator는 이전에 식별 된 개체를 추적합니다. 개체의 ID를 요청하면 ObjectIDGenerator는 기존 ID를 반환할지 아니면 새 ID를 생성하고 기억 할지를 알고 있습니다.

ID는 ObjectIDGenerator 인스턴스의 수명 동안 고유합니다. 일반적으로 ObjectIDGenerator 수명은 자신을 생성 한 Formatter만큼 지속됩니다. 개체 ID는 지정된 직렬화 된 스트림 내에서만 의미가 있으며 직렬화 된 개체 그래프 내에서 다른 개체에 대한 참조가있는 개체를 추적하는 데 사용됩니다.

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

개체 ID는 64 비트 숫자입니다. 할당은 1부터 시작하므로 0은 유효한 개체 ID가 아닙니다. 포맷터는 값이 null 참조 (Visual Basic의 경우 Nothing) 인 개체 참조를 나타 내기 위해 0 값을 선택할 수 있습니다.


5
Reflector는 ObjectIDGenerator가 기본 GetHashCode 구현에 의존하는 해시 테이블이라고 알려줍니다 (즉, 사용자 오버로드를 사용하지 않음).
Anton Tykhyy 2009

인쇄 가능한 고유 ID가 필요할 때 가장 좋은 솔루션 일 것입니다.
Roman Starkov

ObjectIDGenerator는 전화기에서도 구현되지 않습니다.
Anthony Wieser 2012 년

ObjectIDGenerator가 무엇을하는지 정확히 이해하지 못하지만 RuntimeHelpers.GetHashCode를 사용하는 경우에도 작동하는 것 같습니다. 내 경우에는 RuntimeHelpers.GetHashCode 만 테스트했습니다.
다니엘 Bişar

+1-매우 매끄럽게 작동합니다 (최소한 데스크톱에서는).
Hot Licks 2014 년

37

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


2
그것은 도움이 될 수 있지만 비용-IIRC, 기본 개체를 사용하여 GetHashCode ()는 무료가 아닌 동기화 블록을 할당해야합니다. 그래도 좋은 생각입니다-+1.
Jon Skeet

감사합니다.이 방법을 몰랐습니다. 그러나 고유 한 해시 코드도 생성하지 않습니다 (문제의 샘플 코드와 정확히 동일하게 작동 함). 사용자가 해시 코드를 재정의하면 기본 버전을 호출하는 데 유용합니다.
Martin Konicek 2009

1
너무 많이 필요하지 않은 경우 GCHandle을 사용할 수 있습니다 (아래 참조).
Anton Tykhyy 2009

42
존경받는 저자의 .NET에 관한 책은 RuntimeHelpers.GetHashCode ()가 AppDomain 내에서 고유 한 코드를 생성하며 Microsoft에서 GetUniqueObjectID 메서드의 이름을 지정할 수 있다고 말합니다. 이것은 단순히 잘못된 것입니다. 테스트에서 나는 보통 10,000 개의 개체 인스턴스 (WinForms TextBox)를 생성 할 때 복제를 얻었고 30,000 개를 넘을 수 없다는 것을 발견했습니다. 추정 된 고유성에 의존하는 코드는 많은 개체를 생성 한 후 프로덕션 시스템에서 간헐적으로 충돌을 일으켰습니다.
Jan Hettich

3
@supercat : Aha-2003 년부터 .NET 1.0과 1.1에서 나온 몇 가지 증거를 찾았습니다. : 그들은 .NET 2 변화를 계획하고 있었다처럼 보인다 blogs.msdn.com/b/brada/archive/2003/09/30/50396.aspx
존 소총

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

예를 들어 System.Guid.NewGuid ()와 같이 고유 ID로 원하는 것을 선택하거나 가장 빠른 액세스를 위해 단순히 정수를 선택할 수 있습니다.


2
이것이 필요한 것이 Dispose버그라면 어떤 종류의 폐기도 방지 할 수 있기 때문에 도움이되지 않습니다.
Roman Starkov

1
이것은 매우 object.Equals에 대해 동일한 값을 반환 객체 붕괴, 대신 정체성의 사전 사용 평등대로 작동하지 않습니다
안토니 Wieser을

1
그래도 개체가 살아있는 상태로 유지됩니다.
Martin Lottering 2013-06-26

1
@MartinLottering 그가 ConditionalWeakTable <object, idType>을 사용하면 어떻게 될까요?
Demetris Leptos

7

이 방법은 어떻습니까?

첫 번째 개체의 필드를 새 값으로 설정합니다. 두 번째 개체의 동일한 필드에 동일한 값이 있으면 동일한 인스턴스 일 수 있습니다. 그렇지 않으면 다르게 종료하십시오.

이제 첫 번째 개체의 필드를 다른 새 값으로 설정합니다. 두 번째 개체의 동일한 필드가 다른 값으로 변경된 경우 확실히 동일한 인스턴스입니다.

첫 번째 개체의 필드를 종료시 원래 값으로 설정하는 것을 잊지 마십시오.

문제?


4

Visual Studio에서 고유 한 개체 식별자를 만들 수 있습니다. 조사 식 창에서 개체 변수를 마우스 오른쪽 단추로 클릭 하고 상황에 맞는 메뉴에서 개체 ID 만들기를 선택 합니다.

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


이 기능이있는 Visual Studio 버전은 무엇입니까? 예를 들어, Express 버전?
피터 모텐슨

3

이러한 식별자는 인스턴스 내부 또는 외부에서 수동으로 할당해야합니다.

데이터베이스와 관련된 레코드의 경우 기본 키가 유용 할 수 있지만 여전히 중복을 가져올 수 있습니다. 또는을 사용 Guid하거나 자신의 카운터를 유지하여을 사용하여 할당합니다 Interlocked.Increment(오버플로하지 않을만큼 충분히 크게 만듭니다).



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 & ​​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
    }

1
A ConditionalWeakTable는 객체에 대한 참조가 존재하는 동안에 만 객체에 대한 표현을 유지하므로 더 좋을 수 있습니다. 또한 Int64객체에 지속적인 순위를 부여 할 수 있기 때문에는 GUID보다 더 나을 수 있다고 제안합니다 . 이러한 일들이 시나리오를 잠금 유용 할 수있다 (예를 들어, 하나는 다수의 잠금을 획득해야합니다 한 모든 코드는 어떤 정의 위해 그렇게하는 경우 교착 상태를 피할 수 있지만, 그에 대한 작업에가 있어야 정의 된 순서).
supercat

@supercat longs 에 대해 확실히 ; 시나리오에 따라 다릅니다-f.ex. 분산 시스템으로 작업하는 것이 때때로 더 유용합니다 GUID. 에 관해서 ConditionalWeakTable: 당신이 옳습니다; DependentHandle여기에서 유용 할 수있는 활성 상태를 확인합니다 (참고 : 사물의 크기가 조정될 때만!). 성능을 필요로하는 경우 여전히 잠금 그래서 그 경우에 이것을 사용하는 것이 재미있을 수도가 문제가 될 수 있습니다 ... 개인적으로의 이행을 싫어하는 정직 I를 할 수 ConditionalWeakTable있는 간단한을 사용하여 내 편견에, 아마 리드 Dictionary-도 당신 말이 맞지만.
atlaste 2014 년

ConditionalWeakTable실제로 어떻게 작동 하는지 오랫동안 궁금했습니다 . 항목 만 추가 할 수 있다는 사실 때문에 동시성 관련 오버 헤드를 최소화하도록 설계되었다고 생각하지만 내부적으로 어떻게 작동하는지 모르겠습니다. DependentHandle테이블을 사용하지 않는 간단한 래퍼 가 없다는 점이 궁금합니다. 한 개체가 다른 개체의 수명 동안 살아 있는지 확인하는 것이 중요하지만 후자의 개체는 참조 할 여지가 없기 때문입니다. 처음으로.
supercat 2014-01-08

@supercat 나는 그것이 어떻게 작동한다고 생각하는지에 대한 부록을 게시 할 것입니다.
atlaste 2014 년

ConditionalWeakTable테이블에 저장되어있는 허용하지 않는 항목을 수정할 수 있습니다. 따라서 잠금이 아닌 메모리 장벽을 사용하여 안전하게 구현할 수 있다고 생각합니다. 유일한 문제는 두 스레드가 동일한 키를 동시에 추가하려고하는 경우입니다. 항목이 추가 된 후 "add"메소드가 메모리 배리어를 수행하도록 한 다음 정확히 하나의 항목에 해당 키가 있는지 스캔하여 해결할 수 있습니다. 여러 항목에 동일한 키가있는 경우 그 중 하나가 "첫 번째"로 식별 될 수 있으므로 다른 항목을 제거 할 수 있습니다.
supercat 2014-01-09

0

특정 사용에 대한 자신의 코드에 모듈을 작성하는 경우, majkinetor의 방법은 모르지 일했다. 그러나 몇 가지 문제가 있습니다.

첫째 , 공식 문서 는에서 고유 식별자를 반환 한다고 보장 하지 않습니다GetHashCode() ( Object.GetHashCode 메서드 () 참조 ).

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

둘째 , GetHashCode()대부분의 경우에 작동 할 수있는 매우 적은 양의 개체가 있다고 가정하면 이 메서드는 일부 유형에 의해 재정의 될 수 있습니다.
예를 들어, 어떤 클래스 C를 사용하고 있으며 GetHashCode()항상 0을 반환 하도록 재정의 합니다. 그러면 C의 모든 객체가 동일한 해시 코드를 갖게됩니다. 불행하게도 Dictionary, HashTable그리고 몇몇 다른 연관 컨테이너는이 방법을 사용하게됩니다 :

해시 코드는 Dictionary <TKey, TValue> 클래스, 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에서 10,000,000 개의 개체 (위 코드보다 10 배)를 만들 때 개체가 너무 많다고 불평하는 예외를 throw합니다 for.

또한 벤치 마크 결과는 ConditionalWeakTable구현이 ObjectIDGenerator구현 보다 1.8 배 빠르다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.