2017-11-10 18 views
5

예상치 못한 복싱 및 참조 비교와 관련하여 VB.NET에서 비헤이비어가 발생했습니다. 설명하기 위해 모든 유형의 변수를 원자 적으로 업데이트하려고하는 간단한 프로그램을 작성했습니다.개체 권투의 차이점/C#과 VB.Net 간의 참조를 비교하는 것

using System; 

public static class Program 
{ 

    private static object o3; 

    public static void Main() 
    { 
     Console.WriteLine("Hello World"); 

     Test<DateTimeOffset?> value = new Test<DateTimeOffset?>(); 
     Console.WriteLine(value.Value == null); 
     DateTimeOffset dt1 = new DateTimeOffset(2017, 1, 1, 1, 1, 1, TimeSpan.Zero); 
     DateTimeOffset dt2 = new DateTimeOffset(2017, 1, 2, 1, 1, 1, TimeSpan.Zero); 

     Console.WriteLine(value.TrySetValue(null, dt1)); 
     Console.WriteLine(value.Value == dt1); 

     // this should fail 
     Console.WriteLine(value.TrySetValue(null, dt2)); 
     Console.WriteLine(value.Value == dt1); 

     // this should succeed 
     Console.WriteLine(value.TrySetValue(dt1, dt2)); 
    } 
} 

public class Test<T> 
{ 


    public T Value { 
     get { return (T)System.Threading.Volatile.Read(ref _value); } 
    } 


    private object _value; 

    public bool TrySetValue(T oldValue, T newValue) 
    { 
     object curValObj = System.Threading.Volatile.Read(ref _value); 
     if (!object.Equals((T)curValObj, oldValue)) 
      return false; 
     object newValObj = (object)newValue; 
     return object.ReferenceEquals(System.Threading.Interlocked.CompareExchange(ref _value, newValObj, curValObj), curValObj); 
    } 

} 

이 프로그램의 출력은 다음과 같습니다 : 여기

는 C#에서 프로그램 ( https://dotnetfiddle.net/VsMBrg)입니다

Hello World 
True 
True 
True 
False 
True 
True 

이 예상 모든 것이 잘 작동하는 것 같다 같습니다. 여기

Imports System 

Public Module Module1 

    private o3 as object 

    Public Sub Main() 
     Console.WriteLine("Hello World") 

     Dim value As New Test(Of DateTimeOffset?) 
     Console.WriteLine(value.Value is nothing) 
     Dim dt1 As New DateTimeOffset(2017, 1, 1, 1, 1, 1, TimeSpan.Zero) 
     Dim dt2 As New DateTimeOffset(2017, 1, 2, 1, 1, 1, TimeSpan.Zero) 

     Console.WriteLine(value.TrySetValue(Nothing, dt1)) 
     Console.WriteLine(value.Value = dt1) 

     ' This should fail 
     Console.WriteLine(value.TrySetValue(Nothing, dt2)) 
     Console.WriteLine(value.Value = dt1) 

     ' This should succeed 
     Console.WriteLine(value.TrySetValue(dt1, dt2)) 
    End Sub 
End Module 

public class Test(Of T) 


    Public readonly Property Value As T 
     Get 
      Return CType(Threading.Volatile.Read(_value), T) 
     End Get 
    End Property 

    Private _value As Object 

    Public Function TrySetValue(oldValue As T, newValue As T) As Boolean 
     Dim curValObj As Object = Threading.Volatile.Read(_value) 
     If Not Object.Equals(CType(curValObj, T), oldValue) Then Return False 
     Dim newValObj = CObj(newValue) 
     Return Object.ReferenceEquals(Threading.Interlocked.CompareExchange(_value, newValObj, curValObj), curValObj) 
    End Function 

end class 

출력은 다음과 같습니다 : 여기

Hello World 
True 
True 
True 
False 
True 
False 

마지막 문 세트가 작동하지 않았 음을 의미 거짓 다음은 VB.NET (https://dotnetfiddle.net/lasxT2)에서 같은 프로그램입니다. 내가 여기 뭔가 잘못하고있는 중이거나 VB.NET에서 문제가 있습니까?

(참고 :이 예제가 스레딩의 영향을받지 않습니다 그래서 스레드가 없습니다, 휘발성이 읽기/쓰기 무시) : 나는 T는 정수로 변경하면 다음 모든 확인을 작동합니다


편집을
(dotnetfiddle.net/X6uLZs). 나는 사용자 정의 클래스에 T를 변경하는 경우 또한 그것은 또한 OK 작동합니다
dotnetfiddle.net/LnOOme

답변

4

을 나는이 문제의 원인 실제로 어떤 장소에가 C#을 's의 더 유사 VB의 Object 처리, 믿고 dynamic은 보통 Object보다 큽니다.

Public Function TrySetValue(oldValue As T, newValue As T) As Boolean 
    Dim curValObj As Object = _value 'Threading.Volatile.Read(_value) 
    Console.Write(Object.ReferenceEquals(curValObj,_value)) 
    If Not Object.Equals(CType(curValObj, T), oldValue) Then Return False 
    Dim newValObj = CObj(newValue) 
    Return Object.ReferenceEquals(Threading.Interlocked.CompareExchange(_value, newValObj, curValObj), curValObj) 
    End Function 

우리는 Console.WriteLineFalse을 인쇄 할 것으로 기대하지 않을 것이다 : 나는 경우 구체적으로 TrySetValue 다시 작성합니다. 그러나 그것이 바로 그것이하는 일입니다. 이 코드를 다시 C#으로 리플렉터를 사용하여 디 컴파일하면 다음 코드를 얻습니다.

public bool TrySetValue(T oldValue, T newValue) 
{ 
    object objectValue = RuntimeHelpers.GetObjectValue(this._value); 
    Console.Write(object.ReferenceEquals(RuntimeHelpers.GetObjectValue(objectValue), RuntimeHelpers.GetObjectValue(this._value))); 
    if (!object.Equals(Conversions.ToGenericParameter<T>(objectValue), oldValue)) 
    { 
     return false; 
    } 
    object obj3 = newValue; 
    return object.ReferenceEquals(RuntimeHelpers.GetObjectValue(Interlocked.CompareExchange(ref this._value, RuntimeHelpers.GetObjectValue(obj3), RuntimeHelpers.GetObjectValue(objectValue))), RuntimeHelpers.GetObjectValue(objectValue)); 
} 

오, 이런. 여기 GetObjectValue에 대한 모든 호출은 무엇입니까? 글쎄, 효과은 박스형 값 유형으로 복사본을 만들므로 curValObj에는 _value과 같은 개체에 대한 실제 참조가 포함되지 않으므로 Interlocked.CompareExchange은 처리 할 수 ​​없습니다. 실제 객체 참조.

저는 현재이 코드를 다시 작성하여 현재 원하는대로 할 수있는 좋은 방법을 생각할 수 없습니다. CompareExchangeObject 과부하가 우리를 경고하는 이유 그리고 아마도 우리는 이유를 볼 수 있습니다

값의 유형이 오버로드를 사용하지 마십시오.

+0

IL을 보면 C# 명령어는 'unbox.any'이며 여기서 vb 명령어는'Conversions '입니다.ToGenericParameter '. 그래서 VB는 구조체의 복사본을 반환하지만 C#은 동일한 구조체를 참조하고 있음을 의미합니까? – Crowcoder

+0

@Crowcoder -이 작은 코드 샘플에서 만들어진 예상치 못한 복사본의 전단 번호로 어떤 VB 코드가 전혀 작동하는지 질문하게되었습니다 :-( –

+0

CLR 소스 코드에서 : "기술적으로 우리는 boxed DateTimes와 Decimals를 복사하지 않고 반환 할 수 있습니다 여기에서 VB는 고객에게 큰 변화가 될 것이라는 것을 깨달았으므로 복사하십시오. " –