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

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

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?不同,因为这与我正在尝试使用的反射无关。

这段代码只是一个更大项目的一小部分(使用反射完成某些任务),但可以演示问题。

这个示例的特殊之处在于,我们要设置的属性是值类型(结构体),而SetValue似乎无法很好地处理这个问题...

  1. Imports System.Drawing
  2. Module Module1
  3. Sub Main()
  4. Console.WriteLine("Starting test")
  5. Dim s As Size
  6. Console.WriteLine("Created size: {0}", s)
  7. s.Width = 5
  8. Console.WriteLine("Set Width: {0}", s)
  9. Dim pi = s.GetType().GetProperty("Height")
  10. Console.WriteLine("get property info: {0}", pi)
  11. pi.SetValue(s, 10, Nothing)
  12. Console.WriteLine("setting height with pi: {0}", s)
  13. Dim box As Object = s 'same result with RuntimeHelpers.GetObjectValue(s)
  14. Console.WriteLine("boxing: {0}", box)
  15. pi.SetValue(box, 10, Nothing)
  16. Console.WriteLine("setting value: {0}", box)
  17. Console.WriteLine("")
  18. Console.WriteLine("End test")
  19. Console.ReadLine()
  20. End Sub
  21. End Module

给出的结果是:

  1. Starting test
  2. Created size: {Width=0, Height=0}
  3. Set Width: {Width=5, Height=0}
  4. get property info: Int32 Height
  5. setting height with pi: {Width=5, Height=0} 'expected 5,10
  6. boxing: {Width=5, Height=0}
  7. setting value: {Width=5, Height=0} 'expected 5,10
  8. End test

相同的代码在C#中可以正常工作(使用装箱):

  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Console.WriteLine("Starting test");
  6. Size s= new Size();
  7. Console.WriteLine("Created size: {0}", s);
  8. s.Width = 5;
  9. Console.WriteLine("Set Width: {0}", s);
  10. PropertyInfo pi = s.GetType().GetProperty("Height");
  11. Console.WriteLine("get property info: {0}", pi);
  12. Console.WriteLine("can write?: {0}", pi.CanWrite);
  13. pi.SetValue(s, 10, null);
  14. Console.WriteLine("setting height with pi: {0}", s);
  15. Object box = s; //same result with RuntimeHelpers.GetObjectValue(s)
  16. Console.WriteLine("boxing: {0}", box);
  17. pi.SetValue(box, 10, null);
  18. Console.WriteLine("setting value: {0}", box);
  19. Console.WriteLine("");
  20. Console.WriteLine("End test");
  21. Console.ReadLine();
  22. }
  23. }

给出的结果是:

  1. Starting test
  2. Created size: {Width=0, Height=0}
  3. Set Width: {Width=5, Height=0}
  4. get property info: Int32 Height
  5. can write?: True
  6. setting height with pi: {Width=5, Height=0}
  7. boxing: {Width=5, Height=0}
  8. setting value: {Width=5, Height=10}
  9. 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...

  1. Imports System.Drawing
  2. Module Module1
  3. Sub Main()
  4. Console.WriteLine("Starting test")
  5. Dim s As Size
  6. Console.WriteLine("Created size: {0}", s)
  7. s.Width = 5
  8. Console.WriteLine("Set Width: {0}", s)
  9. Dim pi = s.GetType().GetProperty("Height")
  10. Console.WriteLine("get property info: {0}", pi)
  11. pi.SetValue(s, 10, Nothing)
  12. Console.WriteLine("setting height with pi: {0}", s)
  13. Dim box As Object = s 'same result with RuntimeHelpers.GetObjectValue(s)
  14. Console.WriteLine("boxing: {0}", box)
  15. pi.SetValue(box, 10, Nothing)
  16. Console.WriteLine("setting value: {0}", box)
  17. Console.WriteLine("")
  18. Console.WriteLine("End test")
  19. Console.ReadLine()
  20. End Sub
  21. End Module

The result given is:

  1. Starting test
  2. Created size: {Width=0, Height=0}
  3. Set Width: {Width=5, Height=0}
  4. get property info: Int32 Height
  5. setting height with pi: {Width=5, Height=0} 'expected 5,10
  6. boxing: {Width=5, Height=0}
  7. setting value: {Width=5, Height=0} 'expected 5,10
  8. End test

The same code is working (with boxing) in C#

  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Console.WriteLine("Starting test");
  6. Size s= new Size();
  7. Console.WriteLine("Created size: {0}", s);
  8. s.Width = 5;
  9. Console.WriteLine("Set Width: {0}", s);
  10. PropertyInfo pi = s.GetType().GetProperty("Height");
  11. Console.WriteLine("get property info: {0}", pi);
  12. Console.WriteLine("can write?: {0}", pi.CanWrite);
  13. pi.SetValue(s, 10, null);
  14. Console.WriteLine("setting height with pi: {0}", s);
  15. Object box = s; //same result with RuntimeHelpers.GetObjectValue(s)
  16. Console.WriteLine("boxing: {0}", box);
  17. pi.SetValue(box, 10, null);
  18. Console.WriteLine("setting value: {0}", box);
  19. Console.WriteLine("");
  20. Console.WriteLine("End test");
  21. Console.ReadLine();
  22. }
  23. }
  1. Starting test
  2. Created size: {Width=0, Height=0}
  3. Set Width: {Width=5, Height=0}
  4. get property info: Int32 Height
  5. can write?: True
  6. setting height with pi: {Width=5, Height=0}
  7. boxing: {Width=5, Height=0}
  8. setting value: {Width=5, Height=10}
  9. 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中的VB.NET中可以正常工作):

(你需要将.Dump()调用替换为Console.WriteLine或类似的方法)

  1. Dim heightProperty = GetType(Size).GetProperty("Height", BindingFlags.Public Or BindingFlags.Instance)
  2. Dim localStruct As Size
  3. localStruct.Width = 5
  4. localStruct.Dump("Should have Width 5")
  5. ' 由于`vbc`将插入一个隐藏的`PropertyInfo.SetValue(Object, Object)`调用,该调用将对`localStruct`进行装箱,因此在修改后无法检索它,实际上将其丢弃,所以这个`SetValue`调用不起作用。
  6. heightProperty.SetValue(localStruct, 10)
  7. localStruct.Dump(("Should have Height 10, but has " + localStruct.ToString()))
  8. Dim boxedAsObject As System.Object = localStruct
  9. LINQPad.Extensions.Dump(boxedAsObject, "boxed") ' 有趣的是,你不能在Object类型的装箱结构上使用扩展方法语法,很奇怪。
  10. ' 由于`vbc`将插入一个隐藏的`PropertyInfo.SetValue(Object, Object)`调用,该调用将重置`boxedAsObject`,所以这个调用不起作用。
  11. heightProperty.SetValue(boxedAsObject, 10)
  12. LINQPad.Extensions.Dump(boxedAsObject, ("Should have Height 10, but has " + boxedAsObject.ToString()))
  13. ' https://www.pcreview.co.uk/threads/cannot-get-propertyinfo-setvalue-to-work-for-a-structure.1418755/
  14. Dim boxedAsValueType As System.ValueType = localStruct
  15. heightProperty.SetValue(boxedAsValueType, 10)
  16. boxedAsValueType.Dump("Will have Height = 10: " + boxedAsValueType.ToString())

这将给出以下输出:

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


与C#相比,VB.NET在处理装箱结构时有其特殊规则:当你在VB.NET中使用System.Object作为装箱结构的准顶级类型时,IL编译器将插入对RuntimeHelpers.GetObjectValue的隐藏调用,这将导致堆栈上的装箱结构引用被替换为一个新的装箱结构实例。

(以下IL是使用启用了优化的编译)

  1. IL_0000 ldtoken Size
  2. IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
  3. IL_000A ldstr "Height"
  4. IL_000F ldc.i4.s 14 // 20
  5. IL_0011 call Type.GetProperty (String, BindingFlags)
  6. IL_0016 ldloca.s 00 // localStruct
  7. IL_0018 ldc.i4.5
  8. IL_0019 call Size.set_Width (Int32)
  9. IL_001E ldloc.0 // localStruct
  10. IL_001F ldstr "Should have Width 5"
  11. IL_0024 call Extensions.Dump<Size> (Size, String)
  12. IL_0029 pop
  13. IL_002A dup
  14. IL_002B ldloc.0 // localStruct
  15. IL_002C box Size
  16. IL_0031 ldc.i4.s 0A // 10
  17. IL_0033 box Int32
  18. IL_0038 callvirt PropertyInfo.SetValue (Object, Object)
  19. IL_003D ldloc.0 // localStruct
  20. IL_003E ldstr "Should have Height 10, but has "
  21. IL_0043 ldloca.s 00 // localStruct
  22. IL_0045 constrained. Size
  23. IL_004B callvirt Object.ToString ()
  24. IL_0050 call String.Concat (String, String)
  25. IL_0055 call Extensions.Dump<Size> (Size, String)
  26. IL_005A pop
  27. IL_005B ldloc.0 // localStruct
  28. IL_005C box Size
  29. IL_0061 stloc.1 // boxedAsObject
  30. IL_0062 ldloc.1 // boxedAsObject
  31. IL_0063 call RuntimeHelpers.GetObjectValue (Object)
  32. IL_0068 ldstr "boxed"
  33. IL_006D call Extensions.Dump<Object> (Object, String)
  34. IL_0072 pop
  35. IL_0073 dup
  36. IL_0074 ldloc.1 // boxedAsObject
  37. IL_0075 call RuntimeHelpers.GetObjectValue (Object)
  38. IL_007A ldc.i4.s 0A // 10
  39. IL_007C box Int32
  40. IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
  41. IL_0086 ldloc.1 // boxedAsObject
  42. IL_0087 call RuntimeHelpers.GetObjectValue (Object)
  43. IL_008C ldstr "Should have Height 10, but has "
  44. IL_0091 ldloc.1 // boxedAsObject
  45. IL_0092 callvirt Object.ToString ()
  46. IL_0097 call String.Concat (String, String)
  47. IL_009C call Extensions.Dump<Object> (Object, String)
  48. IL_00A1 pop
  49. IL_00A2 ldloc.0 // localStruct
  50. IL_00A3 box Size
  51. IL_00A8 stloc.2 // boxedAsValueType
  52. IL_00A9 ldloc.2 // boxedAsValueType
  53. IL_00AA ldc.i4.s 0A // 10
  54. IL_00AC box Int32
  55. IL_00B1 callvirt PropertyInfo.SetValue (Object, Object)
  56. IL_00B6 ldloc.2 // boxedAsValueType
  57. IL_00B7 ldstr "Will have Height = 10: "
  58. IL_00BC ldloc.2 // boxedAsValueType
  59. IL_00BD callvirt ValueType.ToString ()
  60. IL_00C2 call String.Concat (String, String)
  61. IL_00C7 call Extensions.Dump<ValueType> (ValueType, String)
  62. IL_00CC pop
  63. IL_00CD ret

而等效的C#的IL则没有这些隐藏调用:

  1. IL_0000 ldtoken Size
  2. IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
  3. IL_000A ldstr "Height"
  4. IL_000F ldc.i4.s 14 // 20
  5. IL_0011 call Type.GetProperty (String, BindingFlags)
  6. IL_0016 ldloca.s 00 // localStruct
  7. IL_0018 initobj Size
  8. IL_001E ldloca.s 00 // localStruct
  9. IL_0020 ldc.i4.5
  10. IL_0021 call Size.set_Width (Int32)
  11. IL_0026 ldloc.0 // localStruct
  12. IL_0027 ldstr "Should have Width 5"
  13. IL_002C call Extensions.Dump<Size> (Size, String)
  14. IL_0031 pop
  15. IL_0032 dup
  16. IL_0033 ldloc.0 // localStruct
  17. IL_0034 box Size
  18. IL_0039 ldc.i4.s 0A // 10
  19. IL_003B box Int32
  20. IL_0040 callvirt PropertyInfo.SetValue (Object, Object)
  21. IL_0045 ldloc.0 // localStruct
  22. IL_0046 ldstr "Should have Height 10"
  23. IL_004B call Extensions.Dump<Size> (Size, String)
  24. IL_0050 pop
  25. IL_0051 ldloc.0 // localStruct
  26. IL_0052 box Size
  27. IL_0057 stloc.1 // boxedAsObject
  28. IL_0058 dup
  29. IL_0059 ldloc.1 // boxedAsObject
  30. IL_005A ldc.i4.s 0A // 10
  31. IL_005C box Int32
  32. IL_0061 callvirt PropertyInfo.SetValue (Object, Object)
  33. IL_0066 ldloc.1 // boxedAsObject
  34. IL_0067 ldstr "boxedAsObject"
  35. IL_006C call Extensions.Dump<Object> (Object, String)
  36. IL_0071 pop
  37. IL_0072 ldloc.0 // localStruct
  38. IL_0073 box Size
  39. IL_0078 stloc.2 // boxedAsValueType
  40. IL_0079 ldloc.2 // boxedAsValueType
  41. IL_007A ldc.i4.s 0A // 10
  42. IL_007C box Int32
  43. IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
  44. IL_0086 ldloc.2 // boxedAsValueType
  45. IL_0087 ldstr "boxedAsValueType"
  46. IL_008C call Extensions.Dump<ValueType> (ValueType, String)
  47. IL_0091 pop
  48. IL_0092 ret
英文:

TL;DR: In VB.NET you need to use System.ValueType when working with boxed mutable structs 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)

  1. Dim heightProperty = GetType( Size ).GetProperty( "Height", BindingFlags.Public Or BindingFlags.Instance )
  2. Dim localStruct As Size
  3. localStruct.Width = 5
  4. localStruct.Dump( "Should have Width 5" )
  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.
  6. heightProperty.SetValue( localStruct, 10 )
  7. localStruct.Dump( ( "Should have Height 10, but has " + localStruct.ToString() ) )
  8. Dim boxedAsObject As System.Object = localStruct
  9. LINQPad.Extensions.Dump( boxedAsObject, "boxed" ) ' interestingly, you can't use extension-method syntax on Object-typed boxed structs, weird.
  10. heightProperty.SetValue( boxedAsObject, 10) ' This won't work because `vbc` will insert a hidden `PropertyInfo.SetValue(Object, Object)` call which will reset `boxedAsObject`.
  11. LINQPad.Extensions.Dump( boxedAsObject, ( "Should have Height 10, but has " + boxedAsObject.ToString() ) )
  12. ' https://www.pcreview.co.uk/threads/cannot-get-propertyinfo-setvalue-to-work-for-a-structure.1418755/
  13. Dim boxedAsValueType As System.ValueType = localStruct
  14. heightProperty.SetValue( boxedAsValueType, 10 )
  15. boxedAsValueType.Dump( "Will have Height = 10: " + 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)

  1. IL_0000 ldtoken Size
  2. IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
  3. IL_000A ldstr "Height"
  4. IL_000F ldc.i4.s 14 // 20
  5. IL_0011 call Type.GetProperty (String, BindingFlags)
  6. IL_0016 ldloca.s 00 // localStruct
  7. IL_0018 ldc.i4.5
  8. IL_0019 call Size.set_Width (Int32)
  9. IL_001E ldloc.0 // localStruct
  10. IL_001F ldstr "Should have Width 5"
  11. IL_0024 call Extensions.Dump<Size> (Size, String)
  12. IL_0029 pop
  13. IL_002A dup
  14. IL_002B ldloc.0 // localStruct
  15. IL_002C box Size
  16. IL_0031 ldc.i4.s 0A // 10
  17. IL_0033 box Int32
  18. IL_0038 callvirt PropertyInfo.SetValue (Object, Object)
  19. IL_003D ldloc.0 // localStruct
  20. IL_003E ldstr "Should have Height 10, but has "
  21. IL_0043 ldloca.s 00 // localStruct
  22. IL_0045 constrained. Size
  23. IL_004B callvirt Object.ToString ()
  24. IL_0050 call String.Concat (String, String)
  25. IL_0055 call Extensions.Dump<Size> (Size, String)
  26. IL_005A pop
  27. IL_005B ldloc.0 // localStruct
  28. IL_005C box Size
  29. IL_0061 stloc.1 // boxedAsObject
  30. IL_0062 ldloc.1 // boxedAsObject
  31. IL_0063 call RuntimeHelpers.GetObjectValue (Object)
  32. IL_0068 ldstr "boxed"
  33. IL_006D call Extensions.Dump<Object> (Object, String)
  34. IL_0072 pop
  35. IL_0073 dup
  36. IL_0074 ldloc.1 // boxedAsObject
  37. IL_0075 call RuntimeHelpers.GetObjectValue (Object)
  38. IL_007A ldc.i4.s 0A // 10
  39. IL_007C box Int32
  40. IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
  41. IL_0086 ldloc.1 // boxedAsObject
  42. IL_0087 call RuntimeHelpers.GetObjectValue (Object)
  43. IL_008C ldstr "Should have Height 10, but has "
  44. IL_0091 ldloc.1 // boxedAsObject
  45. IL_0092 callvirt Object.ToString ()
  46. IL_0097 call String.Concat (String, String)
  47. IL_009C call Extensions.Dump<Object> (Object, String)
  48. IL_00A1 pop
  49. IL_00A2 ldloc.0 // localStruct
  50. IL_00A3 box Size
  51. IL_00A8 stloc.2 // boxedAsValueType
  52. IL_00A9 ldloc.2 // boxedAsValueType
  53. IL_00AA ldc.i4.s 0A // 10
  54. IL_00AC box Int32
  55. IL_00B1 callvirt PropertyInfo.SetValue (Object, Object)
  56. IL_00B6 ldloc.2 // boxedAsValueType
  57. IL_00B7 ldstr "Will have Height = 10: "
  58. IL_00BC ldloc.2 // boxedAsValueType
  59. IL_00BD callvirt ValueType.ToString ()
  60. IL_00C2 call String.Concat (String, String)
  61. IL_00C7 call Extensions.Dump<ValueType> (ValueType, String)
  62. IL_00CC pop
  63. IL_00CD ret

...whereas the IL for the equivalent C# lacks those hidden calls:

  1. IL_0000 ldtoken Size
  2. IL_0005 call Type.GetTypeFromHandle (RuntimeTypeHandle)
  3. IL_000A ldstr "Height"
  4. IL_000F ldc.i4.s 14 // 20
  5. IL_0011 call Type.GetProperty (String, BindingFlags)
  6. IL_0016 ldloca.s 00 // localStruct
  7. IL_0018 initobj Size
  8. IL_001E ldloca.s 00 // localStruct
  9. IL_0020 ldc.i4.5
  10. IL_0021 call Size.set_Width (Int32)
  11. IL_0026 ldloc.0 // localStruct
  12. IL_0027 ldstr "Should have Width 5"
  13. IL_002C call Extensions.Dump<Size> (Size, String)
  14. IL_0031 pop
  15. IL_0032 dup
  16. IL_0033 ldloc.0 // localStruct
  17. IL_0034 box Size
  18. IL_0039 ldc.i4.s 0A // 10
  19. IL_003B box Int32
  20. IL_0040 callvirt PropertyInfo.SetValue (Object, Object)
  21. IL_0045 ldloc.0 // localStruct
  22. IL_0046 ldstr "Should have Height 10"
  23. IL_004B call Extensions.Dump<Size> (Size, String)
  24. IL_0050 pop
  25. IL_0051 ldloc.0 // localStruct
  26. IL_0052 box Size
  27. IL_0057 stloc.1 // boxedAsObject
  28. IL_0058 dup
  29. IL_0059 ldloc.1 // boxedAsObject
  30. IL_005A ldc.i4.s 0A // 10
  31. IL_005C box Int32
  32. IL_0061 callvirt PropertyInfo.SetValue (Object, Object)
  33. IL_0066 ldloc.1 // boxedAsObject
  34. IL_0067 ldstr "boxedAsObject"
  35. IL_006C call Extensions.Dump<Object> (Object, String)
  36. IL_0071 pop
  37. IL_0072 ldloc.0 // localStruct
  38. IL_0073 box Size
  39. IL_0078 stloc.2 // boxedAsValueType
  40. IL_0079 ldloc.2 // boxedAsValueType
  41. IL_007A ldc.i4.s 0A // 10
  42. IL_007C box Int32
  43. IL_0081 callvirt PropertyInfo.SetValue (Object, Object)
  44. IL_0086 ldloc.2 // boxedAsValueType
  45. IL_0087 ldstr "boxedAsValueType"
  46. IL_008C call Extensions.Dump<ValueType> (ValueType, String)
  47. IL_0091 pop
  48. IL_0092 ret

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

发表评论

匿名网友

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

确定