英文:
Why is PropertyInfo.SetValue not working in this example and how to make it work?
问题
以下是您要翻译的内容:
这段代码似乎不起作用,尽管根据文档,与装箱相关的部分应该可以正常工作。我似乎无法找到比Value Type Property setting文章中提供的更多信息。
这个问题也不同于Setting a property of a property in a struct?,因为这与反射无关,而我正在尝试使用反射。
这段代码只是一个更大项目的一小部分(使用反射完成某些任务),但它演示了问题。
这个示例的特殊之处在于,我们要设置的属性位于值类型(struct)中,而SetValue似乎处理得不太好...
Imports System.Drawing
Module Module1
Sub Main()
Console.WriteLine("Starting test")
Dim s As Size
Console.WriteLine("Created size: {0}", s)
s.Width = 5
Console.WriteLine("Set Width: {0}", s)
Dim pi = s.GetType().GetProperty("Height")
Console.WriteLine("get property info: {0}", pi)
pi.SetValue(s, 10, Nothing)
Console.WriteLine("setting height with pi: {0}", s)
Dim box As Object = s '同样的结果使用 RuntimeHelpers.GetObjectValue(s)
Console.WriteLine("boxing: {0}", box)
pi.SetValue(box, 10, Nothing)
Console.WriteLine("setting value: {0}", box)
Console.WriteLine("")
Console.WriteLine("End test")
Console.ReadLine()
End Sub
End Module
给出的结果是:
Starting test
Created size: {Width=0, Height=0}
Set Width: {Width=5, Height=0}
get property info: Int32 Height
setting height with pi: {Width=5, Height=0} '期望的结果是5,10
boxing: {Width=5, Height=0}
setting value: {Width=5, Height=0} '期望的结果是5,10
End test
同样的代码在C#中(使用装箱)可以正常工作:
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting test");
Size s = new Size();
Console.WriteLine("Created size: {0}", s);
s.Width = 5;
Console.WriteLine("Set Width: {0}", s);
PropertyInfo pi = s.GetType().GetProperty("Height");
Console.WriteLine("get property info: {0}", pi);
Console.WriteLine("can write?: {0}", pi.CanWrite);
pi.SetValue(s, 10, null);
Console.WriteLine("setting height with pi: {0}", s);
Object box = s; //同样的结果使用 RuntimeHelpers.GetObjectValue(s)
Console.WriteLine("boxing: {0}", box);
pi.SetValue(box, 10, null);
Console.WriteLine("setting value: {0}", box);
Console.WriteLine("");
Console.WriteLine("End test");
Console.ReadLine();
}
}
Starting test
Created size: {Width=0, Height=0}
Set Width: {Width=5, Height=0}
get property info: Int32 Height
can write?: True
setting height with pi: {Width=5, Height=0}
boxing: {Width=5, Height=0}
setting value: {Width=5, Height=10}
End test
如何在VB中使其工作?
英文:
The following code does not seem to work, although according to documentation the part with boxing should work. I seem not be able to find more information on this than the article at Value Type Property setting
This question is also not similar as in Setting a property of a property in a struct? as this has nothing to do with reflection, which I am trying to use.
The code is just a small piece of a much larger project (using reflection to get something done) but demonstrates the issue.
The specialty in this example is that the property we want to set is in a value type (struct) and SetValue seems not to be handling this very well...
Imports System.Drawing
Module Module1
Sub Main()
Console.WriteLine("Starting test")
Dim s As Size
Console.WriteLine("Created size: {0}", s)
s.Width = 5
Console.WriteLine("Set Width: {0}", s)
Dim pi = s.GetType().GetProperty("Height")
Console.WriteLine("get property info: {0}", pi)
pi.SetValue(s, 10, Nothing)
Console.WriteLine("setting height with pi: {0}", s)
Dim box As Object = s 'same result with RuntimeHelpers.GetObjectValue(s)
Console.WriteLine("boxing: {0}", box)
pi.SetValue(box, 10, Nothing)
Console.WriteLine("setting value: {0}", box)
Console.WriteLine("")
Console.WriteLine("End test")
Console.ReadLine()
End Sub
End Module
The result given is:
Starting test
Created size: {Width=0, Height=0}
Set Width: {Width=5, Height=0}
get property info: Int32 Height
setting height with pi: {Width=5, Height=0} 'expected 5,10
boxing: {Width=5, Height=0}
setting value: {Width=5, Height=0} 'expected 5,10
End test
The same code is working (with boxing) in C#
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting test");
Size s= new Size();
Console.WriteLine("Created size: {0}", s);
s.Width = 5;
Console.WriteLine("Set Width: {0}", s);
PropertyInfo pi = s.GetType().GetProperty("Height");
Console.WriteLine("get property info: {0}", pi);
Console.WriteLine("can write?: {0}", pi.CanWrite);
pi.SetValue(s, 10, null);
Console.WriteLine("setting height with pi: {0}", s);
Object box = s; //same result with RuntimeHelpers.GetObjectValue(s)
Console.WriteLine("boxing: {0}", box);
pi.SetValue(box, 10, null);
Console.WriteLine("setting value: {0}", box);
Console.WriteLine("");
Console.WriteLine("End test");
Console.ReadLine();
}
}
Starting test
Created size: {Width=0, Height=0}
Set Width: {Width=5, Height=0}
get property info: Int32 Height
can write?: True
setting height with pi: {Width=5, Height=0}
boxing: {Width=5, Height=0}
setting value: {Width=5, Height=10}
End test
How do I make this work in VB?
答案1
得分: 3
在VB.NET中,当与装箱的可变结构一起工作时,需要使用System.ValueType
而不是System.Object
(在VB.NET中为Object
,在C#中为object
)。将您的代码更改为以下内容,它应该可以工作(至少在针对.NET 7的Linqpad 7中可以工作):
Dim heightProperty = GetType(Size).GetProperty("Height", BindingFlags.Public Or BindingFlags.Instance)
Dim localStruct As Size
localStruct.Width = 5
localStruct.Dump("Should have Width 5")
' 以下的`SetValue`调用不起作用,因为`vbc`将插入一个隐藏的`PropertyInfo.SetValue(Object, Object)`调用,它将对`localStruct`进行装箱,从而在它发生变化后无法检索它,实际上将其丢弃。
heightProperty.SetValue(localStruct, 10)
localStruct.Dump("Should have Height 10, but has " + localStruct.ToString())
Dim boxedAsObject As System.Object = localStruct
LINQPad.Extensions.Dump(boxedAsObject, "boxed") ' 有趣的是,在Object类型的装箱结构上不能使用扩展方法语法。
heightProperty.SetValue(boxedAsObject, 10) ' 这不起作用,因为`vbc`将插入一个隐藏的`PropertyInfo.SetValue(Object, Object)`调用,它将重置`boxedAsObject`。
LINQPad.Extensions.Dump(boxedAsObject, ("Should have Height 10, but has " + boxedAsObject.ToString()))
' https://www.pcreview.co.uk/threads/cannot-get-propertyinfo-setvalue-to-work-for-a-structure.1418755/
Dim boxedAsValueType As System.ValueType = localStruct
heightProperty.SetValue(boxedAsValueType, 10)
boxedAsValueType.Dump("Will have Height = 10: " + boxedAsValueType.ToString())
这将为您提供以下输出:
VB.NET在处理装箱结构时与C#相比有其特殊规则:当您在VB.NET中将System.Object
用作装箱结构的准顶类型时,IL编译器会插入隐藏的调用RuntimeHelpers.GetObjectValue
,这将导致栈上的装箱结构引用被替换为一个新的装箱结构实例。
(以下的IL是使用启用了优化的情况下编译的)
IL_0000 ldtoken Size
IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
IL_000A ldstr "Height"
IL_000F ldc.i4.s 14 // 20
IL_0011 call Type.GetProperty (String, BindingFlags)
IL_0016 ldloca.s 00 // localStruct
IL_0018 ldc.i4.5
IL_0019 call Size.set_Width (Int32)
IL_001E ldloc.0 // localStruct
IL_001F ldstr "Should have Width 5"
IL_0024 call Extensions.Dump<Size> (Size, String)
IL_0029 pop
IL_002A dup
IL_002B ldloc.0 // localStruct
IL_002C box Size
IL_0031 ldc.i4.s 0A // 10
IL_0033 box Int32
IL_0038 callvirt PropertyInfo.SetValue (Object, Object)
IL_003D ldloc.0 // localStruct
IL_003E ldstr "Should have Height 10, but has "
IL_0043 ldloca.s 00 // localStruct
IL_0045 constrained. Size
IL_004B callvirt Object.ToString ()
IL_0050 call String.Concat (String, String)
IL_0055 call Extensions.Dump<Size> (Size, String)
IL_005A pop
IL_005B ldloc.0 // localStruct
IL_005C box Size
IL_0061 stloc.1 // boxedAsObject
IL_0062 ldloc.1 // boxedAsObject
IL_0063 call RuntimeHelpers.GetObjectValue (Object)
IL_0068 ldstr "boxed"
IL_006D call Extensions.Dump<Object> (Object, String)
IL_0072 pop
IL_0073 dup
IL_0074 ldloc.1 // boxedAsObject
IL_0075 call RuntimeHelpers.GetObjectValue (Object)
IL_007A ldc.i4.s 0A // 10
IL_007C box Int32
IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
IL_0086 ldloc.1 // boxedAsObject
IL_0087 call RuntimeHelpers.GetObjectValue (Object)
IL_008C ldstr "Should have Height 10, but has "
IL_0091 ldloc.1 // boxedAsObject
IL_0092 callvirt Object.ToString ()
IL_0097 call String.Concat (String, String)
IL_009C call Extensions.Dump<Object> (Object, String)
IL_00A1 pop
IL_00A2 ldloc.0 // localStruct
IL_00A3 box Size
IL_00A8 stloc.2 // boxedAsValueType
IL_00A9 ldloc.2 // boxedAsValueType
IL_00AA ldc.i4.s 0A // 10
IL_00AC box Int32
IL_00B1 callvirt PropertyInfo.SetValue (Object, Object)
IL_00B6 ldloc.2 // boxedAsValueType
IL_00B7 ldstr "Will have Height = 10: "
IL_00BC ldloc.2 // boxedAsValueType
IL_00BD callvirt ValueType.ToString ()
IL_00C2 call String.Concat (String, String)
IL_00C7 call Extensions.Dump<ValueType> (ValueType, String)
IL_00CC pop
IL_00CD ret
而等效的C# IL不包含这些隐藏的调用:
IL_0000 ldtoken Size
IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
IL_000A ldstr "Height"
IL_000F ldc.i4.s 14 // 20
IL_0011 call Type.GetProperty (String, BindingFlags)
IL_0016 ldloca.s 00 // localStruct
IL_0018 initobj Size
IL_
<details>
<summary>英文:</summary>
**TL;DR:** [In VB.NET you need to use `System.ValueType` when working with boxed mutable structs][1] instead of `System.Object` (aka `Object` in VB.NET and `object` in C#).
Change your code to this and it works (for me, at least, in the VB.NET in Linqpad 7 targeting .NET 7):
(You'll want to replace the `.Dump()` calls with `Console.WriteLine` or similar)
```vb.net
Dim heightProperty = GetType( Size ).GetProperty( "Height", BindingFlags.Public Or BindingFlags.Instance )
Dim localStruct As Size
localStruct.Width = 5
localStruct.Dump( "Should have Width 5" )
' This `SetValue` call below won't work because `vbc` will insert a hidden `PropertyInfo.SetValue(Object, Object)` call which will box `localStruct` such that you can't retrieve it after it's mutated, so effectively discarding it.
heightProperty.SetValue( localStruct, 10 )
localStruct.Dump( ( "Should have Height 10, but has " + localStruct.ToString() ) )
Dim boxedAsObject As System.Object = localStruct
LINQPad.Extensions.Dump( boxedAsObject, "boxed" ) ' interestingly, you can't use extension-method syntax on Object-typed boxed structs, weird.
heightProperty.SetValue( boxedAsObject, 10) ' This won't work because `vbc` will insert a hidden `PropertyInfo.SetValue(Object, Object)` call which will reset `boxedAsObject`.
LINQPad.Extensions.Dump( boxedAsObject, ( "Should have Height 10, but has " + boxedAsObject.ToString() ) )
' https://www.pcreview.co.uk/threads/cannot-get-propertyinfo-setvalue-to-work-for-a-structure.1418755/
Dim boxedAsValueType As System.ValueType = localStruct
heightProperty.SetValue( boxedAsValueType, 10 )
boxedAsValueType.Dump( "Will have Height = 10: " + boxedAsValueType.ToString() )
Gives me this output:
VB.NET has its own special rules for handling boxed structs compared to C#: when you use System.Object
as a _quasi-_top-type for boxed structs in VB.NET then the IL compiler will insert hidden calls to RuntimeHelpers.GetObjectValue
which will cause the boxed struct reference on the stack to be replaced with a new boxed struct instance.
(The IL below was compiled with optimizations enabled)
IL_0000 ldtoken Size
IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
IL_000A ldstr "Height"
IL_000F ldc.i4.s 14 // 20
IL_0011 call Type.GetProperty (String, BindingFlags)
IL_0016 ldloca.s 00 // localStruct
IL_0018 ldc.i4.5
IL_0019 call Size.set_Width (Int32)
IL_001E ldloc.0 // localStruct
IL_001F ldstr "Should have Width 5"
IL_0024 call Extensions.Dump<Size> (Size, String)
IL_0029 pop
IL_002A dup
IL_002B ldloc.0 // localStruct
IL_002C box Size
IL_0031 ldc.i4.s 0A // 10
IL_0033 box Int32
IL_0038 callvirt PropertyInfo.SetValue (Object, Object)
IL_003D ldloc.0 // localStruct
IL_003E ldstr "Should have Height 10, but has "
IL_0043 ldloca.s 00 // localStruct
IL_0045 constrained. Size
IL_004B callvirt Object.ToString ()
IL_0050 call String.Concat (String, String)
IL_0055 call Extensions.Dump<Size> (Size, String)
IL_005A pop
IL_005B ldloc.0 // localStruct
IL_005C box Size
IL_0061 stloc.1 // boxedAsObject
IL_0062 ldloc.1 // boxedAsObject
IL_0063 call RuntimeHelpers.GetObjectValue (Object)
IL_0068 ldstr "boxed"
IL_006D call Extensions.Dump<Object> (Object, String)
IL_0072 pop
IL_0073 dup
IL_0074 ldloc.1 // boxedAsObject
IL_0075 call RuntimeHelpers.GetObjectValue (Object)
IL_007A ldc.i4.s 0A // 10
IL_007C box Int32
IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
IL_0086 ldloc.1 // boxedAsObject
IL_0087 call RuntimeHelpers.GetObjectValue (Object)
IL_008C ldstr "Should have Height 10, but has "
IL_0091 ldloc.1 // boxedAsObject
IL_0092 callvirt Object.ToString ()
IL_0097 call String.Concat (String, String)
IL_009C call Extensions.Dump<Object> (Object, String)
IL_00A1 pop
IL_00A2 ldloc.0 // localStruct
IL_00A3 box Size
IL_00A8 stloc.2 // boxedAsValueType
IL_00A9 ldloc.2 // boxedAsValueType
IL_00AA ldc.i4.s 0A // 10
IL_00AC box Int32
IL_00B1 callvirt PropertyInfo.SetValue (Object, Object)
IL_00B6 ldloc.2 // boxedAsValueType
IL_00B7 ldstr "Will have Height = 10: "
IL_00BC ldloc.2 // boxedAsValueType
IL_00BD callvirt ValueType.ToString ()
IL_00C2 call String.Concat (String, String)
IL_00C7 call Extensions.Dump<ValueType> (ValueType, String)
IL_00CC pop
IL_00CD ret
...whereas the IL for the equivalent C# lacks those hidden calls:
IL_0000 ldtoken Size
IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
IL_000A ldstr "Height"
IL_000F ldc.i4.s 14 // 20
IL_0011 call Type.GetProperty (String, BindingFlags)
IL_0016 ldloca.s 00 // localStruct
IL_0018 initobj Size
IL_001E ldloca.s 00 // localStruct
IL_0020 ldc.i4.5
IL_0021 call Size.set_Width (Int32)
IL_0026 ldloc.0 // localStruct
IL_0027 ldstr "Should have Width 5"
IL_002C call Extensions.Dump<Size> (Size, String)
IL_0031 pop
IL_0032 dup
IL_0033 ldloc.0 // localStruct
IL_0034 box Size
IL_0039 ldc.i4.s 0A // 10
IL_003B box Int32
IL_0040 callvirt PropertyInfo.SetValue (Object, Object)
IL_0045 ldloc.0 // localStruct
IL_0046 ldstr "Should have Height 10"
IL_004B call Extensions.Dump<Size> (Size, String)
IL_0050 pop
IL_0051 ldloc.0 // localStruct
IL_0052 box Size
IL_0057 stloc.1 // boxedAsObject
IL_0058 dup
IL_0059 ldloc.1 // boxedAsObject
IL_005A ldc.i4.s 0A // 10
IL_005C box Int32
IL_0061 callvirt PropertyInfo.SetValue (Object, Object)
IL_0066 ldloc.1 // boxedAsObject
IL_0067 ldstr "boxedAsObject"
IL_006C call Extensions.Dump<Object> (Object, String)
IL_0071 pop
IL_0072 ldloc.0 // localStruct
IL_0073 box Size
IL_0078 stloc.2 // boxedAsValueType
IL_0079 ldloc.2 // boxedAsValueType
IL_007A ldc.i4.s 0A // 10
IL_007C box Int32
IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
IL_0086 ldloc.2 // boxedAsValueType
IL_0087 ldstr "boxedAsValueType"
IL_008C call Extensions.Dump<ValueType> (ValueType, String)
IL_0091 pop
IL_0092 ret
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论