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
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.
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
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.
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