IT이야기

null 인 경우에도 두 "ref"변수가 동일한 변수를 참조하는지 확인하는 방법

cyworld 2021. 3. 26. 20:45
반응형

null 인 경우에도 두 "ref"변수가 동일한 변수를 참조하는지 확인하는 방법은 무엇입니까?


ref변수가 모두 동일한 변수를 참조 하는지 여부를 어떻게 확인할 수 null있습니까?

예:

public static void Main( string[] args )
{
    object a = null;
    object b = null;

    Console.WriteLine( AreSame( ref a, ref b ) ); // Should print False
    Console.WriteLine( AreSame( ref a, ref a ) ); // Should print True
}

static bool AreSame<T1, T2>( ref T1 a, ref T2 b )
{
    // ?????
}

작동하지 않는 내가 시도한 것 :

  • return object.ReferenceEquals( a, b ); (두 테스트 케이스 모두에서 true를 반환)
  • unsafe { return &a == &b; } (관리 객체의 주소를 가져올 수 없음)

실제로 System.Runtime.CompilerServices.Unsafe 패키지 Unsafe.AreSame메서드사용할 수 있습니다 .

이것은 참조를 직접 비교하며 가장 깨끗한 솔루션입니다. 이 메서드는 IL로 작성되었으며 단순히 참조를 비교하기 때문에 IL에서 수 있습니다.

서로 다른 유형의 두 참조를 비교하려면 다음 오버로드를Unsafe.As 사용하여 그중 하나를 캐스팅 할 수 있습니다 .

static bool AreSame<T1, T2>(ref T1 a, ref T2 b) 
    => Unsafe.AreSame(ref Unsafe.As<T1, T2>(ref a), ref b);

참조를 캐스팅하는 것이 복잡하다고 느끼는 경우 또 다른 제안 이 있습니다. 임의의 IL 코드를 C # 코드에 직접 삽입 할 수있는 InlineIL.Fody 라이브러리 를 사용하세요.

static bool AreSame<T1, T2>(ref T1 a, ref T2 b)
{
    IL.Emit.Ldarg(nameof(a));
    IL.Emit.Ldarg(nameof(b));
    IL.Emit.Ceq();
    return IL.Return<bool>();
}

제네릭을 만들 수없고 DynamicMethod동적 형식을 생성해야하기 때문에 Reflection.Emit을 사용하여 런타임에 코드를 내보내는 것보다 쉽기 때문에 이것을 제안 합니다. IL 프로젝트를 작성할 수도 있지만 한 가지 방법에만 과도하게 느껴집니다.

또한 중요한 경우 외부 라이브러리에 대한 종속성을 피할 수 있습니다.


경쟁 조건의 가능성으로 인해 솔루션을 완전히 신뢰 하지는 않습니다 . 이러한 조건을 함께 모을만큼 불행한 경우 :__makerefUnsafe.AsPointer

  • 두 참조가 동일합니다.
  • GC가 다른 스레드에 의해 트리거 된 상기 비교의 일측 평가되지만 전에 다른 하나는
  • 관리되는 힙 어딘가에 참조 포인트
  • 참조 된 객체는 힙 압축을 위해 GC에 의해 이동됩니다.

음, 이미 평가 된 포인터는 비교 전에 GC에서 업데이트되지 않으므로 잘못된 결과가 표시됩니다.

일어날 가능성이 있습니까? 별로. 그러나 그럴 있습니다.

Unsafe.AreSame메서드는 항상 byref 공간에서 작동하므로 GC는 언제든지 참조를 추적하고 업데이트 할 수 있습니다.


안전하지 않은 코드와 문서화되지 않은 __makeref방법을 사용하여 값을 수정하지 않고 방법이 있습니다.

public static void Main(string[] args)
{
    object a = null;
    object b = null;

    Console.WriteLine(AreSame(ref a, ref b));  // prints False
    Console.WriteLine(AreSame(ref a, ref a));  // prints True
}

static bool AreSame<T1, T2>(ref T1 a, ref T2 b)
{
    TypedReference trA = __makeref(a);
    TypedReference trB = __makeref(b);

    unsafe
    {
        return *(IntPtr*)(&trA) == *(IntPtr*)(&trB);
    }
}

참고 : 표현이 *(IntPtr*)(&trA)사실에 의존 TypedReference의 첫 번째 필드가 입니다 IntPtr우리가 비교하려는 변수를 가리키는. 안타깝게도 (또는 다행히?) 해당 필드에 액세스 할 수있는 관리 된 방법이 없습니다. 리플렉션을 사용하더라도 TypedReference상자에 넣을 수 없으므로 .NET과 함께 사용할 수 없습니다 FieldInfo.GetValue.


참조를 임시 변수로 변경하고 다른 변수도 변경되는지 확인하여이 작업을 수행 할 수 있습니다.
빠른 테스트를했는데 작동하는 것 같습니다.

static bool AreSame(ref object a, ref object b) {
    var old_a = a;
    a = new object();
    bool result = object.ReferenceEquals(a, b);
    a = old_a;
    return result;
}

static void Main(string[] args) {
    object a = null;
    object b = null;

    var areSame1 = AreSame(ref a, ref b); // returns false
    var areSame2 = AreSame(ref a, ref a); // returns true
}

문서화되지 않은 __makeref키워드를 사용하지 않는 또 다른 솔루션이 있습니다 . 그러면 System.Runtime.CompilerServices.Unsafe NuGet 패키지 가 사용 됩니다.

using System.Runtime.CompilerServices;

public static void Main( string[] args )
{
    object a = null;
    object b = null;

    Console.WriteLine( AreSame( ref a, ref b ) ); // Prints False
    Console.WriteLine( AreSame( ref a, ref a ) ); // Prints True
}

unsafe static bool AreSame<T1, T2>( ref T1 a, ref T2 b )
{
    var pA = Unsafe.AsPointer( ref a );
    var pB = Unsafe.AsPointer( ref b );

    return pA == pB;
}

참조 URL : https://stackoverflow.com/questions/50831091/how-to-determine-whether-two-ref-variables-refer-to-the-same-variable-even-if

반응형