Difference in object boxing / comparison of links between C # and VB.Net

I came across behavior in VB.NET today regarding boxing and link comparisons, which I did not expect. To illustrate, I wrote a simple program that tries to atomically update a variable of any type.

Here is a C # program ( 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); } } 

The output of this program:

 Hello World True True True False True True 

This is as expected, and everything is working fine. Here is the same program in 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 

Here's the conclusion:

 Hello World True True True False True False 

Here the last statement is false, which means the set is not working. Am I doing something wrong here or is there a problem in VB.NET?

(Note: Ignore volatile reads / write, this example has no threads, so it is not affected by the stream)


Edit: If I change T to an integer, then everything will work fine:
(Dotnetfiddle.net/X6uLZs). Also, if I change T to a user class, it also works fine:
dotnetfiddle.net/LnOOme

+5
source share
1 answer

I believe that the cause of this problem is the handling of the VB Object , where in some places it looks more like C # dynamic than normal Object . In particular, if I rewrite TrySetValue as:

  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 

We never expected Console.WriteLine print False . But that’s exactly what he does. Decompiling this code back to C # (using Reflector), I get this code:

 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 my God. What are all these calls to GetObjectValue here? Well, the effect they have is that the copies must be made from boxed value types, so curValObj never contains an actual reference to the same object as _value , and therefore Interlocked.CompareExchange will never work when we are "Dealing with actual object references."

I can't think of a good way to rewrite this code, at present, to do what you want. And perhaps we can see another reason why overloading Object CompareExchange warns us:

Do not use this overload with type values.

+4
source

Source: https://habr.com/ru/post/1273266/


All Articles