2009-01-15 4 views
5

Extrait de code:Réflexion sur la structure diffère de la classe - mais seulement dans le code

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) 

Cet extrait fonctionne parfaitement si la cible est définie à une instance d'une classe. Cependant, si la cible est définie sur une instance d'une STRUCTURE, le code ne change pas réellement la valeur du champ. Aucune erreur, mais la valeur reste inchangée. Et, bizarrement, si je traverse le code, regarder le SetValue échouer à faire quelque chose, et immédiatement aller à la fenêtre Exécution et tapez exactement la même opération SetValue, qui fonctionne.

Des suggestions sur ce qui se passe et comment changer le champ IN CODE?

Edit:

Par demande de Jon Skeet, code réel:

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 

Variables externes et routines appelées:
* mXmlR est un XmlTextReader et est correctement initialisé et positionné (sinon ce serait ne fonctionne pas sur les classes)
* CreateObject fonctionne (idem)
* XmlDeserialize fonctionne principalement, et au point en question gère très bien un entier. Le seul problème connu est avec les structures. En ce qui concerne la façon dont je vérifie la valeur, je regarde principalement la fenêtre Locals, mais j'ai également utilisé des instructions print dans la fenêtre Immediate, et j'exécute un test NUnit qui échoue en raison de ce problème - tandis que le test équivalent avec une classe, plutôt que d'une structure, passe.

Voici le 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 

Répondre

5

En travaillant avec votre échantillon original, je suis d'accord pour dire que cela fonctionne en C# mais pas en VB! Si vous utilisez Reflector ou ILDasm, vous verrez que l'appel à Field.SetValue (cible, ...) Est en fait compilé (en VB) comme:

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

GetObjectValue « Retourne une copie de la boîte obj si elle est une classe de valeur, sinon obj lui-même est retourné. » C'est à dire. la valeur est définie sur une copie de votre structure!

This link donne l'explication (telle quelle). La solution de contournement consiste à déclarer la cible en tant que System.ValueType au lieu de Object. Je ne suis pas sûr si cela aide réellement dans votre code de la vie réelle: vous pouvez avoir besoin d'un test de type désordonné pour être en mesure de gérer les types de valeur séparément des types de référence.

1

Eh bien, vous n'avez pas montré tout votre code - en particulier, où vous définissez target et comment vous vérifiez la valeur du champ par la suite.

Voici un exemple qui montre qu'il fonctionne bien en C# si:

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); 
    } 
} 

(je suis sûr que le choix de la langue n'est pas pertinente, mais avec plus de code que nous pourrions dire pour certains.) ma forte suspicion est que vous faites quelque chose comme:

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

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

la valeur de foo dans l'extrait ci-dessus ne sera pas changé - parce que sur la deuxième ligne, la valeur est copié quand il est emballé. Vous aurez besoin de Unbox et copier à nouveau plus tard:

foo = (Foo) target; 

Si c'est pas, s'il vous plaît montrer un court mais programme complet qui illustre le problème.

+0

La langue est plutôt pertinente ici: VB.net aurait besoin d'un "ValueType foo = new Foo();" équivalent (au lieu de votre "objet foo = new Foo();" line: pour une raison qui n'est pas claire pour le moment, l'utilisation d'une variable de type objet dans VB.net a un comportement différent). – Giuseppe

3

Le problème est que VB effectue une copie de l'objet et que l'instruction setvalue s'applique à la copie, mais pas à l'objet lui-même. La solution de contournement consiste à restaurer les modifications apportées à l'objet d'origine via une variable auxliar var et la fonction CType. Dans l'exemple suivant, nous voulons mettre le pays domaine de l'champion var Espagne (champion est un * St_WorldChampion structure *). Nous faisons les changements dans le x var, puis nous les copions au champion var. Ça marche.

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 
0

Salut J'ai fait cette fonction en utilisant l'exemple chrétien, j'espère que ça aide. Cette fonction utilise les propriétés qui sont également touchés

''' <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 
Questions connexes