Reflection on structure differs from class - but o

2019-01-28 02:03发布

问题:

Code snippet:

Dim target As Object
' target gets properly set to something of the desired type
Dim field As FieldInfo = target.GetType.GetField("fieldName", _
  BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
field.SetValue(target,newValue)

This snippet works perfectly IF target is set to an instance of a CLASS.

However, if target is set to an instance of a STRUCTURE, the code does not actually change the value of the field. No error, but the value remains unchanged.

And, oddly, if I'm stepping through code, watch the SetValue fail to do anything, and immediately go to the Immediate window and type exactly the same SetValue operation, that works.

Any suggestions on what's going on and how to actually change the field IN CODE?

Edit:

Per request from Jon Skeet, actual code:

Private Shared Function XmlDeserializeObject(ByVal objectType As Type, _
        ByVal deserializedID As String) As Object
    Dim result As Object
    result = CreateObject(objectType)

    mXmlR.ReadStartElement()
    Do While mXmlR.IsStartElement _
    AndAlso mXmlR.Name <> elementItem
        Dim field As FieldInfo = result.GetType.GetField(FullName, _
            BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
        field.SetValue(result, XmlDeserialize(field.FieldType))
    Loop

    Return result
End Function

External variables and called routines:
* mXmlR is an XmlTextReader, and is properly initialized and positioned (else this would not work on classes)
* CreateObject works (ditto)
* XmlDeserialize mostly works, and at the point in question is handling an integer just fine. The only known problem is with structures.

As for how I'm checking the value, I'm mostly looking at the Locals window, but I've also used print statements in the Immediate window, and I'm running an NUnit test which is failing because of this problem - while the equivalent test with a class, rather than a structure, passes.

Here's the test.

<Serializable()> Private Structure SimpleStructure
    Public MemberOne As Integer
End Structure

<Test()> Sub A016_SimpleStructure()
    Dim input As New SimpleStructure
    input.MemberOne = 3
    Dim st As String = Serialize(input)
    Debug.Print(st)
    Dim retObject As Object = Deserialize(st)
    Assert.IsNotNull(retObject)
    Assert.IsInstanceOfType(GetType(SimpleStructure), retObject)
    Assert.AreEqual(input.MemberOne, DirectCast(retObject, SimpleStructure).MemberOne)
End Sub

回答1:

Working with your original sample, I agree that it works in C# but not in VB! If you use Reflector or ILDasm you will see that the call to Field.SetValue(target, ...) is actually compiled (in VB) as:

field.SetValue(RuntimeHelpers.GetObjectValue(target), ...)

GetObjectValue "Returns a boxed copy of obj if it is a value class; otherwise obj itself is returned." I.e. the value is being set on a copy of your struct!

This link gives the explanation (such as it is). The workaround is to declare target as System.ValueType instead of Object. I'm not sure if that actually helps in your real-life code: you may need a messy type test to be able to handle value types separately from reference types.



回答2:

The problem is that VB makes a copy of the object and the setvalue instruction applies to the copy, but not to the object itself. The workaround is to restore the changes to the original object through an auxliar var and the CType function. In the following example, we want to set the country field of the champion var to Spain (champion is a *St_WorldChampion* structure). We make the changes in the x var, an then we copy them to the champion var. It works.

Public Structure St_WorldChampion
    Dim sport As String
    Dim country As String
End Structure

Sub UpdateWorldChampion()
    Dim champion As New St_WorldChampion, x As ValueType
    Dim prop As System.Reflection.FieldInfo

    ' Initial values: Germany was the winner in 2006
    champion.country = "Germany"
    champion.sport = "Football"

    ' Update the World Champion: Spain since 2010
    x = champion
    prop = x.GetType().GetField("country")
    prop.SetValue(x, "Spain")
    champion = CType(x, St_WorldChampion)

End Sub


回答3:

Well, you haven't shown all your code - in particular, where you're setting target and how you're checking the value of the field afterwards.

Here's an example which shows it working fine in C# though:

using System;
using System.Reflection;

struct Foo
{
    public int x;
}

class Test
{
    static void Main()
    {
        FieldInfo field = typeof(Foo).GetField("x");

        object foo = new Foo();
        field.SetValue(foo, 10);
        Console.WriteLine(((Foo) foo).x);
    }
}

(I'm pretty sure the choice of language isn't relevant here, but with more code we could tell for certain.) My strong suspicion is that you're doing something like:

Foo foo = new Foo();
object target = foo;
// SetValue stuff

// What do you expect foo.x to be here?

The value of foo in the snippet above won't have changed - because on the second line, the value is copied when it's boxed. You'd need to unbox and copy again afterwards:

foo = (Foo) target;

If that's not it, please show a short but complete program which demonstrates the problem.



回答4:

Hi I made this function using christian example, hope it helps. This Function uses Properties which also are affected

''' <summary>
''' Establece el -valor- en la -propiedad- en el -objeto-
''' Sets Value in Objeto.[propertyname]
''' </summary>
''' <param name="objeto">Object where we will set this property</param>
''' <param name="Propiedad">Name of the property</param>
''' <param name="valor">New Value of the property</param>
''' <returns>Object with changed property</returns>
''' <remarks>It works on structures!</remarks>
Function Establecer_propiedad(objeto As Object, Propiedad As String, valor As Object) As Object
    'Check arguments
    If objeto Is Nothing Then Throw New ArgumentNullException("Objeto")
    If String.IsNullOrWhiteSpace(Propiedad) Then Throw New ArgumentNullException("Propiedad")
    'Get the object type
    Dim t As Type = objeto.GetType
    'Get the propertyInfo by its name
    Dim prop As PropertyInfo = t.GetProperty(Propiedad)
    'Check if the property exist
    If prop Is Nothing Then Throw New InvalidOperationException("Property does not exist")
    If Not prop.CanWrite Then Throw New InvalidOperationException("Property is read only")
    'Determine if it is a class or a structure
    If Not t.IsValueType Then ' (it is a reference value)
        'Set without troubles
        If prop.CanWrite Then prop.SetValue(objeto, valor)
        'Return object
        Return objeto
    Else '(It is a structure)
        'Create a box using a valuetype
        'It doesnot work in object
        Dim Box As ValueType
        'Set item in box
        Box = objeto
        'Set value in box
        prop.SetValue(Box, valor)
        'Return box
        Return Box
    End If
End Function