vb.net 将 Object 变量传递给需要 Object 参数的方法的可靠方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18000254/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reliable way to pass Object variable to method that expects Object parameter
提问by supercat
The normal expected semantics of a reference type is that it should behave as an object identifier. If some variable holds a reference to the 5483rd object a program created, passing that variable to a method should give it a reference to the 5483rd object. VB.NET mostly works like this, but with a rather curious exception: even in the Option Strict Ondialect, attempting pass such a variable of type Objectto a method which takes a parameter of that type, copy one Objectvariable to another, or otherwise cause an "rvalue" of type Object[to borrow C terminology] to be stored to an "lvalue" of that type, will sometimes result in the compiler storing a reference to a different object.
引用类型的正常预期语义是它应该表现为对象标识符。如果某个变量持有对程序创建的第 5483 个对象的引用,则将该变量传递给方法应该给它一个对第 5483 个对象的引用。VB.NET 大多是这样工作的,但有一个相当奇怪的例外:即使在Option Strict On方言中,尝试将这种类型的变量传递Object给采用该类型参数的方法,将一个Object变量复制到另一个变量,或者以其他方式导致“右值类型Object[借用 C 术语] 要存储到该类型的“左值”中,有时会导致编译器存储对不同对象的引用。
Is there any nice way to avoid this in cases where code does not know, and shouldn't have to care, about the types of objects it's dealing with?
在代码不知道也不应该关心它正在处理的对象类型的情况下,有没有什么好的方法可以避免这种情况?
If one can have either of the involved operands be of a type other than Object, there is no problem. Withina generic method, variables of its generic type will also work correctly even when that type happens to be Object. Parameters which are of the generic type, however, will be treated as Objectwhen the type is invoked using that type.
如果可以将任何一个涉及的操作数设为 以外的类型Object,则没有问题。 在泛型方法中,即使该类型恰好是Object. 但是,泛型类型的参数将被视为Object使用该类型调用该类型时。
Consider the methods:
考虑以下方法:
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
One would expect that the first function would return a WeakReference that will remain alive as long as the passed-in object is. One would further expect that if the second function is given a WeakReference that is still alive, the method would return a reference that would keep it alive. Unfortunately, that assumption will fail if the reference refers to a boxed non-primitive value type. In that case, the first method will return a weak reference to a new copy of the boxed value that won't be kept alive by the original reference, and the second will return a new copy of the boxed value that won't keep the one in the weak reference alive.
人们会期望第一个函数会返回一个 WeakReference,只要传入的对象存在,它就会保持活动状态。人们会进一步期望,如果给第二个函数一个仍然活着的 WeakReference,该方法将返回一个引用,使其保持活着。不幸的是,如果引用引用了一个装箱的非原始值类型,那么这个假设就会失败。在这种情况下,第一个方法将返回一个对不会被原始引用保持活动的装箱值的新副本的弱引用,而第二个方法将返回不会保持原引用的装箱值的新副本一个在弱引用活着。
If one changed the methods to be generic:
如果将方法更改为泛型:
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
that would avoid the problem within the methods, even if one were to invoke MakeNewWeakReference(Of Object)or GetWeakReferenceTargetOrDefault(Of Object). Unfortunately, if one were to try to use either method with a parameter of type Object, and either the thing being stored (in the first case) or the variable it was being stored to (in the second) was also type Object, the problem would still occur at the method's invocation or when storing its return value. If one put all of one's code into a generic class and only ever used it with a type parameter of Object, but made sure to always TryCastthings of type Objectto the generic type (such operation should never actually fail if the generic type happens to be Object) that would work to solve the problem, but would be rather ugly. Is there a clean way to specify that a variable should be allowed to hold a reference to any type of heap object the way Objectcan, but should always behave with reference semantics the way all other reference types do?
这将避免方法中的问题,即使要调用MakeNewWeakReference(Of Object)或GetWeakReferenceTargetOrDefault(Of Object)。不幸的是,如果要尝试使用带有 type 参数的任一方法Object,并且存储的东西(在第一种情况下)或存储到的变量(在第二种情况下)也是 type Object,问题仍然存在在方法调用或存储其返回值时发生。如果将一个人的所有代码都放入一个泛型类中,并且只将它与 Object 的类型参数一起使用,但要确保TryCast类型的事物始终Object是泛型类型(如果泛型类型恰好是这样的操作实际上永远不会失败)Object) 可以解决问题,但会相当难看。有没有一种干净的方法来指定一个变量应该被允许以这种方式保存对任何类型的堆对象的Object引用,但应该总是像所有其他引用类型一样使用引用语义?
BTW, some directly-runnable test code:
顺便说一句,一些可直接运行的测试代码:
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
There's no real reason why the type of object given to the ObjTest(Object)method should have to care what kind of object it's given, but all three tests that print truewith a class object like Stringor a primitive value type like Int32fail with a non-primitive value type like Decimal. Is there any nice way to fix that?
没有真正的理由为什么给ObjTest(Object)方法的对象类型应该关心它给定的对象类型,但是true使用类对象(如类对象String或原始值类型)打印的所有三个测试Int32都因非原始值类型而失败Decimal. 有什么好的方法可以解决这个问题吗?
采纳答案by varocarbas
(I removed all this part because it is not applicable anymore to the new text of the question)
(我删除了所有这部分,因为它不再适用于问题的新文本)
--- SAMPLE CODE (original question)
--- 示例代码(原始问题)
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
--- ANSWER TO THE UPDATED QUESTION
--- 回答更新的问题
I have to recognise that I am somehow impressed with the results you are showing. I haven't ever looked at all this in detail but the fact of having two different treatments for two different groups of types; and getting to the point of provoking ReferenceEquals(sameObject, sameObject) = Falseis certainly curious. Just a quick summary of your example:
我必须承认我对你展示的结果印象深刻。我从来没有详细研究过这一切,但对两种不同的类型进行两种不同的治疗这一事实;达到挑衅的地步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
Making an Object Typevariable going through this condition is as easy as doing O1 = 2D. You have also observed that, in these cases, the WeakReferencehas to be defined slightly different: wr = New WeakReference(CType(quirkyObj, ValueType)).
使Object Type变量经历这种情况就像做一样容易O1 = 2D。您还观察到,在这些情况下,WeakReference必须定义略有不同:wr = New WeakReference(CType(quirkyObj, ValueType))。
All this is certainly interesting (more than what I thought before reading this last question :)), although can be avoided by relying on codes like this one (or the one above):
所有这一切当然很有趣(比我在阅读最后一个问题之前想到的更有趣:)),尽管可以通过依赖这样的代码(或上面的代码)来避免:
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
Which can be used like this:
可以这样使用:
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)
This approach looks just at values and thus being quirky or not does not really matter (Decimalis quirky but neither Doublenor Integerand this method works fine with all of them).
这种方法只着眼于值,因此是否古怪并不重要(Decimal古怪但既不是Double也不是Integer,这种方法对所有值都适用)。
In summary: before reading your example, I would have said: avoid problems and just use objects as "temporary holders of values", convert them into the target type ASAP and deal with the target type. After reading your answer, I do recognise that your methodology seems quite solid ("pretty ugly"? Why? I like the ReferenceEqualsapproach but if you don't like it and just want to determine if the type is primitive you can rely on O1.GetType().IsPrimitive) and might have some applicability. I cannot come up with a better way to do things than in your example: you are able to locate the "quirky" types and to keep a WeakReference. I guess that this is the maximum you can get under these conditions.
总结:在阅读您的示例之前,我会说:避免问题,只需将对象用作“值的临时持有者”,尽快将它们转换为目标类型并处理目标类型。阅读您的答案后,我确实认识到您的方法论似乎很可靠(“相当丑陋”?为什么?我喜欢这种ReferenceEquals方法,但如果您不喜欢它并且只想确定类型是否是原始类型,您可以依赖O1.GetType().IsPrimitive)和可能有一定的适用性。我想不出比你的例子更好的做事方法:你能够找到“古怪”的类型并保持一个弱引用。我想这是在这些条件下您可以获得的最大值。
回答by Mark Hurd
Note that VB injects calls to System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValuewhich is documentedto exactly do what you're noticing: Return a "boxed copyof objif it is a value class; otherwise objitself is returned."
需要注意的是VB调用内喷射到System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue该记录确切地做什么你发现:返回一个“盒装拷贝的obj,如果它是一个值类;否则obj将返回自己。”
In fact the documentation goes on to say, if the value type is immutable it returns the same object passed in. (It does not explain how immutability is determined, and it's an InternalCall:-( )
事实上,文档继续说,如果值类型是不可变的,它会返回传入的相同对象。(它没有解释不变性是如何确定的,它是一个InternalCall:-( )
It seems Decimal, Date, and, of course, a user-defined Structureare seen as mutable by the CLR.
似乎Decimal, Date, 当然,用户定义的Structure被 CLR 视为可变的。
To actually attempt to answer your question: VB.NET does not call GetObjectValue, but rather directly uses the MSIL boxcommand when using a generic type:
实际尝试回答您的问题: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
This does NOT write anything for the types I've tried, but GetObjectValueis called at the callsite :-(
这不会为我尝试过的类型写任何内容,而是GetObjectValue在调用站点调用 :-(
(BTW This is one case where ReferenceEqualsis available when Isis not.)
(顺便说一句,这是一种ReferenceEquals可用时Is不可用的情况。)
The comments from the 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.
I don't have further comments to make at this stage -- just consolidating into one place.
在这个阶段我没有进一步的评论——只是合并到一个地方。
I had this link stored in my Chrome bookmarks: RuntimeHelpers.GetObjectValue why needed. I don't recall how long ago I stored it.
我将此链接存储在我的 Chrome 书签中:RuntimeHelpers.GetObjectValue 为什么需要。我不记得我保存了多久。

