2

参照型の通常の期待されるセマンティクスは、オブジェクト識別子として動作する必要があるということです。プログラムが作成した 5483 番目のオブジェクトへの参照を変数が保持している場合、その変数をメソッドに渡すと、5483 番目のオブジェクトへの参照が与えられます。VB.NET はほとんどこのように動作しますが、興味深い例外があります:Option Strict On方言でも、その型の変数Objectをその型のパラメーターを取るメソッドに渡そうとしたり、あるObject変数を別の変数にコピーしたり、そうでなければ "rvalueObjectその型の「左辺値」に格納される [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 を返すと予想されます。さらに、2 番目の関数にまだ生きている WeakReference が与えられた場合、メソッドはその関数を生きたままにする参照を返すことが期待されます。残念ながら、参照がボックス化された非プリミティブ値型を参照している場合、その仮定は失敗します。その場合、最初のメソッドは、元の参照によって保持されないボックス化された値の新しいコピーへの弱い参照を返し、2 番目のメソッドは、ボックス化された値の新しいコピーを返します。弱参照の 1 つが生きています。

メソッドをジェネリックに変更した場合:

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

またはを呼び出す場合でも、メソッド内の問題を回避できます。残念ながら、いずれかのメソッドを type のパラメーターで使用しようとして、格納されているもの (最初のケース) または格納されている変数 (2 番目のケース) も typeであった場合、問題は依然として発生します。メソッドの呼び出し時または戻り値の格納時に発生します。すべてのコードをジェネリック クラスに入れ、それを Object の型パラメーターでのみ使用した場合、常に型のものをジェネリック型にするようにします (そのような操作は、たまたまジェネリック型がMakeNewWeakReference(Of Object)GetWeakReferenceTargetOrDefault(Of Object)ObjectObjectTryCastObjectObject)それは問題を解決するのに役立ちますが、かなり醜いでしょう. 変数が任意のタイプのヒープオブジェクトへの参照を保持できるようにする必要があることを指定する明確な方法はありますが、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またはプリミティブ値の型で出力される3つのテストはすべて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

--- 更新された質問への回答

私はあなたが示している結果に何らかの形で感銘を受けていることを認識しなければなりません. 私はこれらすべてを詳細に調べたことはありませんが、2 つの異なるタイプのグループに対して 2 つの異なる処理を行うという事実です。そして、挑発するところまで行くの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風変わりですが、どちらDoubleでもなくInteger、この方法はそれらすべてでうまく機能します)。

要約すると、例を読む前に、問題を回避し、オブジェクトを「値の一時的な保持者」として使用し、できるだけ早くターゲットタイプに変換して、ターゲットタイプを処理すると言いました。あなたの答えを読んだ後、私はあなたの方法論が非常にしっかりしているように見えることを認識しています(「かなり醜い」?なぜですか?私はReferenceEqualsアプローチが好きですが、それが好きではなく、型がプリミティブであるかどうかを判断したい場合は、信頼できますO1.GetType().IsPrimitive)そして応用性があるかもしれません。あなたの例よりも良い方法を思いつくことはできません。「風変わりな」タイプを見つけて、WeakReferenceを保持することができます。この条件で得られる最大値だと思います。

于 2013-08-01T18:35:13.317 に答える
1

VB は、あなたが気付いていることを正確に行うために文書化されている呼び出しを挿入することに注意System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValueください。objobj

実際、ドキュメントには、値の型が不変の場合、渡されたのと同じオブジェクトが返されると書かれていますInternalCall

DecimalDate、そしてもちろん、ユーザー定義はStructureCLR によって変更可能と見なされます。

実際にあなたの質問に答えようとすると: 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利用できる1 つのケース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.

この段階ではこれ以上のコメントはありません。1 つの場所に統合するだけです。

このリンクを Chrome のブックマークに保存しました: RuntimeHelpers.GetObjectValue why needed . どのくらい前に保管したか覚えていません。

于 2014-02-19T05:00:14.100 に答える