2017-11-10 25 views
5

Tôi gặp phải một hành vi trong VB.NET hôm nay liên quan đến boxing và so sánh tham chiếu mà tôi không mong đợi. Để minh họa, tôi đã viết một chương trình đơn giản cố gắng cập nhật nguyên tử biến bất kỳ loại nào.Sự khác biệt trong đối tượng boxing/so sánh tài liệu tham khảo giữa C# và VB.Net

Dưới đây là một chương trình trong C# (https://dotnetfiddle.net/VsMBrg):

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

} 

Kết quả của chương trình này là:

Hello World 
True 
True 
True 
False 
True 
True 

Đây là như mong đợi và mọi thứ dường như làm việc tốt. Dưới đây là chương trình tương tự trong VB.NET (https://dotnetfiddle.net/lasxT2):

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 

Ở đây, đầu ra là:

Hello World 
True 
True 
True 
False 
True 
False 

đây báo cáo kết quả cuối cùng là sai có nghĩa là các thiết lập đã không làm việc. Tôi có làm gì sai ở đây hay là vấn đề trong VB.NET?

(Lưu ý: Bỏ qua dễ bay hơi lần đọc/viết, ví dụ này không có chủ đề để nó không bị ảnh hưởng bởi luồng)


Edit: Nếu tôi thay đổi T để nguyên sau đó mọi thứ hoạt động OK:
(dotnetfiddle.net/X6uLZs). Ngoài ra nếu tôi thay đổi T để một lớp tùy chỉnh nó cũng làm việc OK:
dotnetfiddle.net/LnOOme

Trả lời

4

Tôi tin rằng nguyên nhân của vấn đề này thực sự là Object xử lý VB, nơi ở một số nơi nó tương tự hơn để C# 's dynamic so với đồng bằng Object. Cụ thể, nếu tôi lại viết TrySetValue như:

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 

Chúng tôi sẽ không bao giờ hy vọng rằng Console.WriteLine in False. Nhưng đó là chính xác những gì nó làm. Giải mã mã này trở lại C# (sử dụng Reflector), tôi nhận mã này:

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

Oh dear. Tất cả các cuộc gọi này là gì để GetObjectValue làm ở đây? Vâng, hiệu ứng mà chúng đang có là gây ra các bản sao được tạo thành từ các loại giá trị được đóng hộp và vì vậy, không bao giờ chứa tham chiếu thực tế cho cùng một đối tượng như _value đề cập, và do đó Interlocked.CompareExchange không bao giờ có thể hoạt động khi chúng ta đang xử lý tham chiếu đối tượng thực tế.

Tôi không thể nghĩ ra một cách hay để viết lại mã này, hiện tại, để làm những gì bạn muốn. Và có lẽ chúng ta có thể thấy thêm lý do tại sao Object quá tải của CompareExchange cảnh báo chúng ta:

Không sử dụng quá tải này với các loại giá trị.

+0

Nhìn vào IL, lệnh C# là 'unbox.any' trong đó lệnh vb là' Chuyển đổi.ToGenericParameter '. Vì vậy, điều đó có nghĩa là vb đang trả về một bản sao của cấu trúc nhưng C# đang tham chiếu cùng một cấu trúc, đúng không? – Crowcoder

+0

@Crowcoder - số lượng cắt các bản sao không mong muốn được tạo ra trong mẫu mã nhỏ này đã khiến tôi đặt câu hỏi về cách mã VB hoạt động :-( –

+0

Từ mã nguồn CLR: "Về mặt kỹ thuật, chúng tôi có thể trả về ngày và giờ thập phân được đóng hộp mà không sao chép chúng ở đây, nhưng VB nhận ra rằng đây sẽ là một thay đổi đột phá cho khách hàng của họ. Vì vậy, hãy sao chép chúng. " –

Các vấn đề liên quan