将Object变量传递给期望Object参数的方法的可靠方法

时间:2013-08-01 17:03:32

标签: vb.net object struct boxing

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

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

如果任何一个涉及的操作数可以是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)GetWeakReferenceTargetOrDefault(Of Object),也可以避免方法中的问题。不幸的是,如果要尝试使用类型为Object的参数的任一方法,并且存储的东西(在第一种情况下)或存储到的变量(在第二种情况下)也是类型Object,问题仍然发生在方法的调用或存储其返回值时。如果将一个人的所有代码放入一个泛型类中,并且只将其与类型参数Object一起使用,但确保始终TryCast类型为Object的事物为泛型类型(此类操作应该永远不会实际上如果泛型类型碰巧是Object)会失败,这会解决问题,但会相当丑陋。是否有一种干净的方式来指定应该允许变量以Object的方式保存对任何类型的堆对象的引用,但是应该始终使用引用语义来表达所有其他引用类型的方式吗?

BTW,一些可直接运行的测试代码:

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的测试都没有真正的原因。{{ 1}}或类似String的原始值类型失败,其中非原始值类型如Int32。有什么好办法解决这个问题吗?

2 个答案:

答案 0 :(得分:2)

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

--- SAMPLE CODE(原始问题)

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。我想这是你在这些条件下可以获得的最大值。

答案 1 :(得分:1)

请注意,VB会将System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue的{​​{1}}注入documented来完全按照您的注意事项进行操作:如果有obj,请返回“{{1>}的盒装副本是一个值类;否则返回obj本身。“

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

似乎DecimalDate,当然,CLR认为用户定义的Structure是可变的。

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

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可用的一种情况。)

reference source的评论:

  // 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 why needed。我不记得多久以前我存储它了。