2014-09-02 18 views
5

Wie kann ich Wert in Struct-Feld - myStruct.myField mit Reflektion mit DynamicMethod? Wenn ich setter(myStruct, 111) aufrufen, wurde der Wert nicht festgelegt, da MyStruct Werttyp ist. Console.WriteLine(myStruct.myField) zeigt den Wert 3.
Wie kann man die GetDelegate Methode ändern, um den Wert in myStruct.myField einzustellen?C# Reflection - Wie Feld Wert für Struktur festlegen

public struct MyStruct 
{ 
    public int myField; 
} 

public delegate void SetHandler(object source, object value); 

private static SetHandler GetDelegate(Type type, FieldInfo fieldInfo) 
{ 
    DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true); 
    ILGenerator setGenerator = dm.GetILGenerator(); 

    setGenerator.Emit(OpCodes.Ldarg_0); 
    setGenerator.DeclareLocal(type); 
    setGenerator.Emit(OpCodes.Unbox_Any, type); 
    setGenerator.Emit(OpCodes.Stloc_0); 
    setGenerator.Emit(OpCodes.Ldloca_S, 0); 
    setGenerator.Emit(OpCodes.Ldarg_1); 
    setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); 
    setGenerator.Emit(OpCodes.Stfld, fieldInfo); 
    setGenerator.Emit(OpCodes.Ldloc, 0); 
    setGenerator.Emit(OpCodes.Box, type); 
    setGenerator.Emit(OpCodes.Ret); 
    return (SetHandler)dm.CreateDelegate(typeof(SetHandler)); 
} 

MyStruct myStruct = new MyStruct(); 
myStruct.myField = 3; 

FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance); 

SetHandler setter = GetDelegate(typeof(MyStruct), fi); 
setter(myStruct, 111); 
Console.WriteLine(myStruct.myField); 
+1

Deshalb arbeiten wir nicht mit veränderlichen Strukturen. Sie mutieren eine Kopie der Struktur. Erstellen Sie eine neue Version der Struktur mit den initialisierten Feldern zu dem, was sie sein sollen. – Servy

+1

Related [setzen Sie ein Feld von struct] (http://Stackoverflow.com/questions/1272454/generate-dynamic-method-to-set-a-field-of-a-struct-instead-of-using-reflection? rq = 1) Frage * kann * sein, was Sie suchen ... Seitennotiz: freundlich gestellte Frage mit gutem Beispiel ... aber das Ganze, was Sie erreichen wollen, ist sehr verwirrend und funktioniert in den meisten Fällen nicht so, wie Sie es wünschen ... –

+0

Hoppla! Ich erkenne, dass ich meine Antwort vermasselt hatte; bearbeitet - aber es würde viel besser funktionieren als 'ref MyStruct' –

Antwort

10

Bearbeiten: I made this mistake again - Spaß Tatsache; unbox-any gibt den Wert zurück; unbox gibt den Zeiger auf die Daten zurück, der in-Place muate ermöglicht.

Hier ist die Arbeits IL Generation:

setGenerator.Emit(OpCodes.Ldarg_0); 
    setGenerator.Emit(OpCodes.Unbox, type); 
    setGenerator.Emit(OpCodes.Ldarg_1); 
    setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); 
    setGenerator.Emit(OpCodes.Stfld, fieldInfo); 
    setGenerator.Emit(OpCodes.Ret); 

Aber! Dies mutiert eine Box-Kopie; Sie würden danach unbox brauchen:

object obj = myStruct; 
    setter(obj, 111); 
    MyStruct andBackAgain = (MyStruct)obj; 
    Console.WriteLine(andBackAgain.myField); 
    Console.WriteLine(myStruct.myField); 

Um es zu tun an Ort und Stelle, würden Sie wahrscheinlich brauchen die Methode eine ref MyStruct zu nehmen oder zu Rückkehr ein MyStruct. Sie könnte die Box-Kopie zurückgeben, aber das macht es nicht viel einfacher zu bedienen. Ehrlich gesagt, es ist strittig: Strukturen sollten im Allgemeinen nicht veränderbar sein.

+0

+1 für die Verwendung von IL - das ist auf meiner "zu lernen" -Liste ... – petelids

+0

@petelids um fair zu sein, habe ich nur die IL des OP, aber: Ja, ich mache * viele * von IL - sehr mächtig für Meta-Programmierung. Ich verwende es in Protobuf-Net, Dapper, Fast-Member und einigen anderen Bibliotheken. –

+0

Bearbeitete Antwort funktioniert! andBackAgain.myField ist 111. Aber myStruct.myField ist immer 3. Gibt es eine andere Möglichkeit, den Wert für myStruct.myField festzulegen, ohne ref MyStruct zu verwenden, wenn Reflektion mit hoher Leistung verwendet wird? – Cyrus

6

Ich denke, wie Servy in den Kommentaren hervorhebt, ist es wahrscheinlich besser, keine veränderbare Struktur zu haben und stattdessen eine Kopie der Struktur mit dem neuen Wert zu erstellen. Sie können die SetValueDirect-Methode auf der FieldInfo-Instanz verwenden, wenn Sie wirklich wollen Reflection zu mutieren die Struktur, aber es verwendet die undokumentierte __makeref Methode, um eine TypedReference zu erhalten.

Ich würde wirklich nicht empfehlen, diesen Code zu verwenden; es ist rein zu zeigen, dass dies möglich ist:

MyStruct myStruct = new MyStruct(); 
myStruct.myField = 3; 

FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance); 
TypedReference reference = __makeref(myStruct); 
fi.SetValueDirect(reference, 111); 
Console.WriteLine(myStruct.myField); //prints 111 
+0

Was ist das, ich nicht einmal ... +1 Für die "__makeref" Keyword kann ich nicht glauben, dass ich nie darüber gestolpert. Du hast mir nur eine Menge Sachen zum Spielen gegeben. – Groo

+0

@Groo Es gibt mehr undokumentierte Schlüsselwörter. '__arglist',' __refvalue', '__reftype'. Einfach googeln :) –

+0

Danke @Groo - Ich denke, ich sah es auf [Eric Lippert] (http://ericlippert.com/) Blog irgendwo und es muss stecken geblieben sein (Sorry, ich kann den direkten Link nicht finden). Ich habe es nie anders benutzt, als mit zu spielen, und ich glaube nicht, dass ich es jemals tun werde. – petelids

0

zu anderen Antworten hinzuzufügen, können Sie tatsächlich innerhalb der Delegat Box, solange Ihre Methode auch die modifizierte Struktur zurückgibt.

Da mein IL-fu ist nicht so toll, das ist, wie Sie es mit plain tun würde:

// the good side is that you can use generic delegates for added type safety 
public delegate T SetHandler<T>(T source, object value); 

private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo) 
{ 
    return (s, val) => 
    { 
     object obj = s; // we have to box before calling SetValue 
     fieldInfo.SetValue(obj, val); 
     return (T)obj; 
    }; 
} 

Dies bedeutet, müssen Sie den Rückgabewert wie folgt holen:

SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi); 
myStruct = setter(myStruct, 111); 
Console.WriteLine(myStruct.myField); 

Aber es ist nicht notwendig, es vor dem Aufruf der setter zu boxen.

Alternativ können Sie die Struktur mit dem ref Schlüsselwort übergeben, was würde:

public delegate void SetHandler<T>(ref T source, object value); 

private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo) 
{ 
    return (ref T s, object val) => 
    { 
     object obj = s; 
     fieldInfo.SetValue(obj, val); 
     s = (T)obj; 
    }; 
} 

SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi); 
setter(ref myStruct, 111); // no need to return anymore 
Console.WriteLine(myStruct.myField); 
+0

Ich habe viele Strukturen und eine Methode void SetStructValue (Objekt someStruct, Objektwert). Wie kann ich Ihre Methode GetDelegate in meiner Methode SetStructValue aufrufen. Ich kann GetDelegate (fi) nicht aufrufen, da der Typ von someStruct nicht mit typeof identisch ist (MyStruct). Typeof someStruct kann alles sein - jede Struktur! – Cyrus

+0

Wenn Ihre Struktur bereits in ein 'Objekt' eingereiht ist, dann hat es keinen Sinn, Generika zu verwenden, und Sie können die Beispiele aus anderen Antworten verwenden. Wenn Sie jedoch vor dem Aufruf von 'SetStructValue' eine Variable vom Typ value haben, sollten Sie überlegen, diese Methode auch generisch zu machen. – Groo

1

Hier ist der Code mit ref:

public delegate void SetHandler<T>(ref T source, object value) where T : struct; 

     private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo) where T : struct 
     { 
      var type = typeof(T); 
      DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { type.MakeByRefType(), typeof(object) }, type, true); 
      ILGenerator setGenerator = dm.GetILGenerator(); 

      setGenerator.Emit(OpCodes.Ldarg_0); 
      setGenerator.DeclareLocal(type); 
      setGenerator.Emit(OpCodes.Ldarg_0); 
      setGenerator.Emit(OpCodes.Ldnull); 
      setGenerator.Emit(OpCodes.Stind_Ref); 
      setGenerator.Emit(OpCodes.Ldarg_1); 
      setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); 
      setGenerator.Emit(OpCodes.Stfld, fieldInfo); 
      setGenerator.Emit(OpCodes.Ldloc, 0); 
      setGenerator.Emit(OpCodes.Box, type); 
      setGenerator.Emit(OpCodes.Ret); 
       return (SetHandler<T>)dm.CreateDelegate(typeof(SetHandler<>).MakeGenericType(type)); 
     } 

     static void Main(string[] args) 
     { 
      MyStruct myStruct = new MyStruct(); 
      myStruct.myField = 3; 

      FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance); 

      var setter = GetDelegate<MyStruct>(fi); 
      setter(ref myStruct, 111); 
      Console.WriteLine(myStruct.myField); 
     } 
+0

Funktioniert! Schöne Lösung mit Ref-Parameter. – Cyrus

+0

Können Sie eine Lösung ohne den generischen Typ in Delegat schreiben? Ich kann Delegat 'öffentlichen Delegaten void SetHandler verwenden (ref Objekt Quelle, Objekt Wert)' – Cyrus

0

Der einfachste Weg, Reflection Felder verwenden zu setzen oder Eigenschaften einer Struktur besteht darin, die Struktur zu boxen, die Box-Struktur an SetField oder SetProperty weiterzuleiten und dann die Struktur zu entpacken, sobald alle gewünschten Manipulationen abgeschlossen sind.

public static class refStructTest 
{ 
    struct S1 
    { 
     public int x; 
     public int y { get; set; } 
     public override string ToString() 
     { 
      return String.Format("[{0},{1}]", x, y); 
     } 
    } 
    public static void test() 
    { 
     var s = default(S1); 
     s.x = 2; 
     s.y = 3; 
     Object obj = s; 
     var fld = typeof(S1).GetField("x"); 
     var prop = typeof(S1).GetProperty("y"); 
     fld.SetValue(obj,5); 
     prop.SetValue(obj,6,null); 
     s = (S1)obj; 
     Console.WriteLine("Result={0}", s); 
    } 
} 

Nach der ECMA-Dokumentation wird jeder Wert Typ mit zwei Arten von Dingen verbunden: ein Speicherplatztyp und einem Haufen Objekttyp. Der Heap-Objekttyp verhält sich wie alle Heap-Objekttypen mit Referenzsemantik. Die Übergabe eines Verweises auf ein Heap-Objekt an eine Methode wie SetValue wird somit das Objekt ändern, an das die Referenz übergeben wurde.

Hinweis für VB-Benutzer: VB.NET hat ein wirklich ärgerliches Verhalten, das fast Sinn im Option Strict On Dialekt machen, die aber existiert auch im Option Strict Off Dialekt: Wenn ein Variable Kompilierung-Typs Object, die hält einen Verweis auf einem Die Boxed-Struktur wird einer anderen Variablen desselben Typs zugewiesen oder als Parameter des Typs Object übergeben. VB.NET speichert oder übergibt einen Verweis auf eine Kopie des ursprünglichen Objekts. Wenn Sie Code wie oben in VB.NET schreiben, sollte man obj vom Typ ValueType statt Object machen, um solches Verhalten zu verhindern.

+0

Wie Sie Ihre Methode aktualisieren, wenn die Eingabe ist struct: public static void test (S1 s) ... aber nicht verwenden (ref S1 s) – Cyrus

+0

Meinst du "wie man eine Methode updatet eine externe Struktur:' public static void test (ref S1 s) ', aber * note * Verwendung von' (ref S1 s) '? Ich habe mich wahrscheinlich" nicht "für vertippt "merke" öfter, als ich mich erinnern kann, aber es ändert natürlich die Bedeutung völlig. – supercat