2

引用类型的正常预期语义是它应该表现为对象标识符。如果某个变量包含对程序创建的第 5483 个对象的引用,则将该变量传递给一个方法应该给它一个对第 5483 个对象的引用。VB.NET 大多是这样工作的,但有一个相当奇怪的例外:即使在Option Strict On方言中,尝试将这种类型的变量传递Object给采用该类型参数的方法,将一个Object变量复制到另一个变量,或者以其他方式导致“右值” " 类型Object[借用 C 术语] 要存储到该类型的“左值”,有时会导致编译器存储对不同对象的引用。

在代码不知道也不应该关心它正在处理的对象类型的情况下,有什么好的方法可以避免这种情况吗?

如果可以让任何一个涉及的操作数属于 以外的类型Object,则没有问题。 泛型方法中,其泛型类型的变量也将正常工作,即使该类型恰好是Object. 但是,属于泛型类型的参数将被视为Object使用该类型调用该类型时。

考虑以下方法:

Function MakeNewWeakReference(Obj As Object) As WeakReference
   Return New WeakReference(Obj)
End Function 

Function GetWeakReferenceTargetOrDefault(WR as WeakReference, DefaultValue as Object) _
     As Object
   Dim WasTarget as Object = WR.Target
   If WasTarget IsNot Nothing Then Return WasTarget
   Return DefaultValue
End Function

人们会期望第一个函数将返回一个 WeakReference,只要传入的对象存在,它就会保持活动状态。人们会进一步期望,如果给第二个函数一个仍然存在的 WeakReference,则该方法将返回一个使其保持活跃的引用。不幸的是,如果引用引用的是装箱的非原始值类型,那么该假设将失败。在这种情况下,第一个方法将返回一个对装箱值的新副本的弱引用,该副本不会被原始引用保持活动状态,第二个方法将返回一个新的装箱值副本,该副本不会保留一个在弱引用活着。

如果将方法更改为通用方法:

Function MakeNewWeakReference(Of T As Class)(Obj As T) As WeakReference
   Return New WeakReference(Obj)
End Function 

Function GetWeakReferenceTargetOrDefault(Of T As Class)(WR as WeakReference, _
             DefaultValue as T) As T
   Dim WasTarget as T = TryCast(WR.Target, T)
   If WasTarget IsNot Nothing Then Return WasTarget
   Return DefaultValue
End Function

这将避免方法中的问题,即使要调用MakeNewWeakReference(Of Object)or GetWeakReferenceTargetOrDefault(Of Object)。不幸的是,如果尝试使用带有 type 参数的任一方法Object,并且存储的东西(在第一种情况下)或存储到的变量(在第二种情况下)也是 type Object,问题仍然存在发生在方法调用或存储其返回值时。如果将一个人的所有代码放入一个泛型类中,并且只将它与 Object 的类型参数一起使用,但要确保始终使用泛型TryCast类型Object的类型(如果泛型类型恰好是这样的操作实际上不应该失败)Object) 这将有助于解决问题,但会相当难看。有没有一种干净的方法来指定一个变量应该被允许以可以的方式保存对任何类型的堆对象的Object可以,但应该始终像所有其他引用类型一样使用引用语义?

顺便说一句,一些可直接运行的测试代码:

Sub ObjTest(O1 As Object)
    Debug.Print("Testing type {0} (value is {1})", O1.GetType, O1)
    Dim O2 As Object
    Dim wr As New WeakReference(O1)

    O2 = O1 ' source and destination are type Object--not identity preserving

    Debug.Print("Ref-equality after assignment: {0}", O2 Is O1)
    Debug.Print("Ref-equality with itself: {0}", Object.ReferenceEquals(O1, O1))
    GC.Collect()
    Debug.Print("Weak reference still alive? {0}", wr.IsAlive)
    Debug.Print("Value was {0}", O1) ' Ensure O1 is still alive
End Sub

Sub ObjTest()
    ObjTest("Hey")
    ObjTest(1)
    ObjTest(1D)
End Sub

没有真正的理由为什么给ObjTest(Object)方法的对象类型应该关心它给定的对象类型,但是所有三个true使用类对象String或原始值类型打印的测试都会Int32失败,因为非原始值类型Decimal. 有什么好的方法可以解决这个问题吗?

4

2 回答 2

2

(我删除了所有这部分,因为它不再适用于问题的新文本)

--- 示例代码(原始问题)

Public Class Form1

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Dim Obj As Object

        'Argument as Object treated as String
        Obj = "converting into string although still is an object"
        Dim outString As String = ObjToString(Obj)

        'Is, complex behaviour; equals (=), always the same
        Obj = "This one is 1"
        Dim is1 As Integer = IsVsEqual(Obj, False)  '1
        Dim equal1 As Integer = IsVsEqual(Obj, True) '1
        Obj = 1.0d 'This one 2
        Dim outIndex2 As Integer = IsVsEqual(Obj, False) '2
        Dim equal2 As Integer = IsVsEqual(Obj, True)  '1

    End Sub

    Private Function ObjToString(obj As Object) As String

        Dim nowIWantString As String = obj.ToString()
        nowIWantString = nowIWantString & " -- now string 100%"

        Return nowIWantString
    End Function

    Private Function IsVsEqual(obj As Object, tryEqual As Boolean) As Integer

        Dim obj2 As Object = obj
        Dim outIndex As Integer = 0
        If (tryEqual) Then
            If (obj2 = obj) Then
                outIndex = 1
            Else
                outIndex = 2
            End If
        Else
            If (obj2 Is obj) Then
                outIndex = 1
            Else
                outIndex = 2
            End If
        End If

    Return outIndex
End Function

End Class

--- 回答更新后的问题

我必须承认,我对你所展示的结果印象深刻。我从来没有详细研究过这一切,但是对于两组不同的类型有两种不同的治疗方法;达到挑衅的地步ReferenceEquals(sameObject, sameObject) = False当然很好奇。只是对您的示例的快速总结:

Dim O1 As Object = new Object
If Not Object.ReferenceEquals(O1, O1) Then 
    'This object will show the "quirky behaviour"
End If

使Object Type变量通过这种条件就像做一样容易O1 = 2D。您还观察到,在这些情况下,WeakReference必须对 的定义略有不同:wr = New WeakReference(CType(quirkyObj, ValueType)).

所有这一切当然很有趣(比我在阅读最后一个问题之前的想法:)),尽管可以通过依赖像这样的代码(或上面的代码)来避免:

Public Function dealWithNumObjects(a As Object) As Object

    Dim outObject As Object = a

    If (TypeOf a Is Double) Then
        'Do operations as double
    ElseIf (TypeOf a Is Decimal) Then
        'Do operations as decimal
    ElseIf (TypeOf a Is Integer) Then
        'Do operations as integer
    End If

    Return outObject
End Function

可以这样使用:

Dim input As Object
input = 5D 'Decimal
Dim outputDecimal As Decimal = DirectCast(dealWithNumObjects(input), Decimal)
input = 5.0 'Double
Dim outputDouble As Double = DirectCast(dealWithNumObjects(input), Double)
input = 5 'Integer
Dim outputInteger As Integer = DirectCast(dealWithNumObjects(input), Integer)

这种方法只关注值,因此是否古怪并不重要(Decimal很古怪,但也不DoubleInteger,而且这种方法适用于所有这些值)。

总结:在阅读您的示例之前,我会说:避免问题并仅将对象用作“值的临时持有者”,尽快将它们转换为目标类型并处理目标类型。阅读您的答案后,我确实认识到您的方法似乎很可靠(“非常丑陋”?为什么?我喜欢这种ReferenceEquals方法,但如果您不喜欢它并且只想确定类型是否是可以依赖的原始类型O1.GetType().IsPrimitive)和可能有一定的适用性。我想不出比您的示例更好的做事方式:您可以找到“古怪”类型并保留 WeakReference。我想这是您在这些条件下可以获得的最大值。

于 2013-08-01T18:35:13.317 回答
1

请注意,VB 注入的调用System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue记录在案以完全按照您的要求进行:返回“如果它是值类的盒装副本;否则返回自身”。objobj

事实上,文档继续说,如果值类型是不可变的,它会返回传入的相同对象。(它没有解释不可变性是如何确定的,它是一个InternalCall:-( )

似乎Decimal, Date,当然,用户定义Structure的被 CLR 视为可变的。

要实际尝试回答您的问题:VB.NET 不调用GetObjectValue,而是box在使用泛型类型时直接使用 MSIL 命令:

Sub Assign(Of T)(ByRef lvalue As T, ByRef rvalue As T)
  lvalue = rvalue
  If Not Object.ReferenceEquals(lvalue, rvalue) Then _
    Console.WriteLine("Ref-equality lost even generically!")
End Sub

这不会为我尝试过的类型编写任何内容,而是GetObjectValue在调用站点调用:-(

(顺便说一句,这是一种在ReferenceEquals不可用时可用Is的情况。)

来自参考来源的评论:

  // GetObjectValue is intended to allow value classes to be manipulated as 'Object'
  // but have aliasing behavior of a value class.  The intent is that you would use
  // this function just before an assignment to a variable of type 'Object'.  If the
  // value being assigned is a mutable value class, then a shallow copy is returned
  // (because value classes have copy semantics), but otherwise the object itself
  // is returned.
  //
  // Note: VB calls this method when they're about to assign to an Object
  // or pass it as a parameter.  The goal is to make sure that boxed
  // value types work identical to unboxed value types - ie, they get
  // cloned when you pass them around, and are always passed by value.
  // Of course, reference types are not cloned.

在这个阶段我没有进一步的评论 - 只是合并到一个地方。

我将此链接存储在我的 Chrome 书签中:RuntimeHelpers.GetObjectValue 为什么需要。我不记得我存储它多久了。

于 2014-02-19T05:00:14.100 回答