为什么 PropertyInfo.SetValue 在这个示例中不起作用,如何使其起作用?

huangapple go评论104阅读模式
英文:

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())

这将为您提供以下输出:

为什么 PropertyInfo.SetValue 在这个示例中不起作用,如何使其起作用?

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&#39;ll want to replace the `.Dump()` calls with `Console.WriteLine` or similar)

```vb.net
Dim heightProperty = GetType( Size ).GetProperty( &quot;Height&quot;, BindingFlags.Public Or BindingFlags.Instance )

Dim localStruct As Size
localStruct.Width = 5
localStruct.Dump( &quot;Should have Width 5&quot; )

&#39; This `SetValue` call below won&#39;t work because `vbc` will insert a hidden `PropertyInfo.SetValue(Object, Object)` call which will box `localStruct` such that you can&#39;t retrieve it after it&#39;s mutated, so effectively discarding it.
heightProperty.SetValue( localStruct, 10 )
localStruct.Dump( ( &quot;Should have Height 10, but has &quot; + localStruct.ToString() ) )

Dim boxedAsObject As System.Object = localStruct
LINQPad.Extensions.Dump( boxedAsObject, &quot;boxed&quot; ) &#39; interestingly, you can&#39;t use extension-method syntax on Object-typed boxed structs, weird.

heightProperty.SetValue( boxedAsObject, 10) &#39; This won&#39;t work because `vbc` will insert a hidden `PropertyInfo.SetValue(Object, Object)` call which will reset `boxedAsObject`.
LINQPad.Extensions.Dump( boxedAsObject, ( &quot;Should have Height 10, but has &quot; + boxedAsObject.ToString() )  )

&#39; 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( &quot;Will have Height = 10: &quot; + boxedAsValueType.ToString() )

Gives me this output:

为什么 PropertyInfo.SetValue 在这个示例中不起作用,如何使其起作用?


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	&quot;Height&quot;
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	&quot;Should have Width 5&quot;
IL_0024	call	Extensions.Dump&lt;Size&gt; (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	&quot;Should have Height 10, but has &quot;
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&lt;Size&gt; (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	&quot;boxed&quot;
IL_006D	call	Extensions.Dump&lt;Object&gt; (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	&quot;Should have Height 10, but has &quot;
IL_0091	ldloc.1	   // boxedAsObject
IL_0092	callvirt	Object.ToString ()
IL_0097	call	String.Concat (String, String)
IL_009C	call	Extensions.Dump&lt;Object&gt; (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	&quot;Will have Height = 10: &quot;
IL_00BC	ldloc.2	   // boxedAsValueType
IL_00BD	callvirt	ValueType.ToString ()
IL_00C2	call	String.Concat (String, String)
IL_00C7	call	Extensions.Dump&lt;ValueType&gt; (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	&quot;Height&quot;
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	&quot;Should have Width 5&quot;
IL_002C	call	Extensions.Dump&lt;Size&gt; (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	&quot;Should have Height 10&quot;
IL_004B	call	Extensions.Dump&lt;Size&gt; (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	&quot;boxedAsObject&quot;
IL_006C	call	Extensions.Dump&lt;Object&gt; (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	&quot;boxedAsValueType&quot;
IL_008C	call	Extensions.Dump&lt;ValueType&gt; (ValueType, String)
IL_0091	pop	
IL_0092	ret

huangapple
  • 本文由 发表于 2023年8月9日 15:14:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76865423-2.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定