2014-09-02 19 views
5

Come posso impostare il valore nel campo struct - myStruct.myField con reflection usando DynamicMethod? Quando chiamo il valore setter(myStruct, 111) non è stato impostato, perché MyStruct è il tipo di valore. Console.WriteLine(myStruct.myField) mostra il valore 3.
Come modificare il metodo GetDelegate per impostare il valore in myStruct.myField?C# Reflection - Come impostare il valore del campo per struct

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

Questo è il motivo per cui non lavoriamo con le strutture mutevoli. Stai mutando una copia della struttura. Crea una nuova versione della struct con i campi inizializzati per quello che vuoi che siano. – Servy

+1

correlati [imposta un campo di struct] (http://stackoverflow.com/questions/1272454/generate-dynamic-method-to-set-a-field-of-a-struct-instead-of-using-reflection? rq = 1) domanda * potrebbe * essere quello che stai cercando ... Nota a margine: domanda ben fatta con un buon campione ... ma l'intera cosa che stai cercando di ottenere è molto confusa e improbabile che funzioni come vuoi nella maggior parte dei casi ... –

+0

Oops; Mi rendo conto di aver rovinato la mia risposta; modificato - tuttavia, funzionerebbe molto meglio come un 'ref MyStruct' –

risposta

10

Modifica: I made this mistake again - fatto divertente; unbox-any restituisce il valore ; unbox restituisce il puntatore ai dati - che consente la modifica del posto.

Ecco il lavoro di generazione IL:

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

Ma! Questo sta mutando una copia in scatola; si avrebbe bisogno di unboxing seguito:

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

di farlo al suo posto, si sarebbe probabilmente necessario il metodo di prendere un ref MyStruct, o per ritorno un MyStruct. È restituire la copia in scatola, ma ciò non rende molto più facile da usare. Francamente, è discutibile: le strutture non dovrebbero essere generalmente modificabili.

+0

+1 per l'utilizzo di IL - che è sulla mia lista" da imparare "... – petelids

+0

@petelids per essere onesti, ho appena corretto l'IL dell'OP, ma: sì, faccio * un sacco * di IL - molto potente per la meta-programmazione. Lo uso in protobuf-net, dapper, fast-member e poche altre librerie. –

+0

La risposta modificata funziona! andBackAgain.myField è 111. Ma myStruct.myField è sempre 3. Esiste un altro modo per impostare il valore per myStruct.myField senza utilizzare Reftrtr con riflesso con prestazioni elevate? – Cyrus

6

Penso che come Servy fa notare nei commenti è probabilmente meglio non avere una struttura mutevole e invece creare una copia della struttura con il nuovo valore. È possibile utilizzare il metodo SetValueDirect nell'istanza FieldInfo se si utilizza lo per riflettere per modificare la struttura ma utilizza il metodo non documentato __makeref per ottenere uno TypedReference.

Io davvero non consiglierei di usare questo codice; è puramente per dimostrare che questo è possibile:

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

Cos'è che non faccio nemmeno ... +1 Per la parola chiave '__makeref', non posso credere di non averlo mai inciampato su quello. Mi hai appena dato un sacco di cose con cui giocare. – Groo

+0

@Groo Ci sono altre parole chiave non documentate. '__arglist',' __refvalue', '__reftype'. Basta google :) –

+0

Grazie @Groo - Penso di averlo visto sul blog di [Eric Lippert] (http://ericlippert.com/) da qualche parte e deve essersi bloccato (mi dispiace, non riesco a trovare il collegamento diretto). Non l'ho mai usato a parte giocare e penso che non lo farò mai :) – petelids

0

Per aggiungere altre risposte, si può effettivamente scatola dentro il delegato, a patto che il metodo restituisce anche la struct modificato.

Poiché la mia IL-fu che non è grande, questo è come si farebbe con la riflessione semplice:

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

Ciò significa che sarà necessario per recuperare il valore di ritorno in questo modo:

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

Ma non è necessario inserirlo prima di chiamare lo setter.

In alternativa, è possibile passare la struct utilizzando la parola chiave ref, che si tradurrebbe in:

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

Ho molte strutture e un metodo void SetStructValue (object someStruct, valore dell'oggetto). Come posso chiamare il metodo GetDelegate nel mio metodo SetStructValue. Non riesco a chiamare GetDelegate (fi) perché il tipo di someStruct non è lo stesso di typeof (MyStruct). Typeof someStruct può essere everythig - any struct! – Cyrus

+0

Se la tua struttura è già racchiusa in un 'oggetto', allora non ha senso usare i generici e puoi usare gli esempi mostrati in altre risposte. Ma se hai una variabile di tipo valore prima di chiamare il tuo 'SetStructValue', allora dovresti considerare di rendere anche quel metodo generico. – Groo

1

Ecco il codice con rif:

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

Funziona! Bella soluzione con parametro ref. – Cyrus

+0

È possibile scrivere una soluzione senza il tipo generico in delegato? Posso usare delegato 'delegato pubblico vuoto SetHandler (oggetto oggetto ref, valore oggetto)' – Cyrus

0

Il modo più semplice per utilizzare la riflessione per impostare i campi o le proprietà di una struttura è di inscatolare la struttura, passare la struttura in scatola a SetField o SetProperty e quindi rimuovere la struttura una volta terminate tutte le manipolazioni desiderate.

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

Secondo la documentazione ECMA, ogni tipo di valore è associato con due tipi di cose: un tipo di percorso di archiviazione e di un tipo di oggetto mucchio. Il tipo di oggetto heap, come tutti i tipi di oggetti heap, si comporterà con la semantica di riferimento; passare un riferimento a un oggetto heap a un metodo come SetValue modificherà quindi l'oggetto a cui è stato passato il riferimento.

Nota per gli utenti di VB: VB.NET ha un comportamento davvero fastidioso che quasi senso nel Option Strict On dialettale, ma che esiste anche nel Option Strict Off dialettale: se una variabile di tempo di compilazione tipo Object che contiene un riferimento a un la struttura scatolata viene assegnata a un'altra variabile dello stesso tipo o passata come parametro di tipo Object, VB.NET memorizzerà o passerà un riferimento a una copia dell'oggetto originale. Quando si scrive codice come sopra in VB.NET, si dovrebbe rendere obj di tipo ValueType anziché Object per impedire tale comportamento.

+0

Come aggiornare il metodo, se l'input è struct: public static void test (S1 s) ... ma non utilizzare (ref S1 s) – Cyrus

+0

Intendevi "come fare un metodo per aggiornare una struttura esterna:' test statico pubblico vuoto (ref S1 s) ', ma * nota * uso di' (ref S1 s) '? Probabilmente ho sbagliato a digitare" not "per "nota" più volte di quanto possa ricordare, ma ovviamente cambia completamente il significato. – supercat