C# 和 VB.Net 之间对象装箱/比较引用的差异
Difference in object boxing / comparing references between C# and VB.Net
我今天在 VB.NET 中遇到了一个我没想到的关于装箱和引用比较的行为。为了说明,我写了一个简单的程序,它试图自动更新任何类型的变量。
这是一个 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);
}
}
这个程序的输出是:
Hello World
True
True
True
False
True
True
这是预期的,一切似乎都正常。
这是 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
这里的输出是:
Hello World
True
True
True
False
True
False
这里最后一个语句是假的,这意味着这个集合没有起作用。
我是在这里做错了什么还是 VB.NET 中的问题?
(注意:忽略Volatile reads/writes,本例没有线程所以不受线程影响)
编辑:
如果我将 T 更改为整数,那么一切正常:
(dotnetfiddle.net/X6uLZs)。
另外,如果我将 T 更改为自定义 class,它也可以正常工作:
dotnetfiddle.net/LnOOme
我认为这个问题的 原因 实际上是 VB 的 Object
处理,在某些地方它更类似于 C# 的 dynamic
而不是普通的 Object
。具体来说,如果我将 TrySetValue
重写为:
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.WriteLine
打印 False
。但这正是它的作用。将此代码反编译回 C#(使用 Reflector),我获得此代码:
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
永远不会包含对与[=20=相同对象的实际引用] 引用,因此当我们处理实际对象引用时 Interlocked.CompareExchange
永远无法工作。
目前我想不出重写这段代码来做你想做的事情的好方法。也许我们可以看到 CompareExchange
的 Object
过载警告我们的 进一步 原因:
Do not use this overload with value types.
我今天在 VB.NET 中遇到了一个我没想到的关于装箱和引用比较的行为。为了说明,我写了一个简单的程序,它试图自动更新任何类型的变量。
这是一个 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);
}
}
这个程序的输出是:
Hello World
True
True
True
False
True
True
这是预期的,一切似乎都正常。 这是 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
这里的输出是:
Hello World
True
True
True
False
True
False
这里最后一个语句是假的,这意味着这个集合没有起作用。 我是在这里做错了什么还是 VB.NET 中的问题?
(注意:忽略Volatile reads/writes,本例没有线程所以不受线程影响)
编辑:
如果我将 T 更改为整数,那么一切正常:
(dotnetfiddle.net/X6uLZs)。
另外,如果我将 T 更改为自定义 class,它也可以正常工作:
dotnetfiddle.net/LnOOme
我认为这个问题的 原因 实际上是 VB 的 Object
处理,在某些地方它更类似于 C# 的 dynamic
而不是普通的 Object
。具体来说,如果我将 TrySetValue
重写为:
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.WriteLine
打印 False
。但这正是它的作用。将此代码反编译回 C#(使用 Reflector),我获得此代码:
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
永远不会包含对与[=20=相同对象的实际引用] 引用,因此当我们处理实际对象引用时 Interlocked.CompareExchange
永远无法工作。
目前我想不出重写这段代码来做你想做的事情的好方法。也许我们可以看到 CompareExchange
的 Object
过载警告我们的 进一步 原因:
Do not use this overload with value types.