2012-03-29 10 views
9

In realtà, avrei dovuto chiedere: come posso fare questo e rimanere CLS compatibile? Perché l'unico modo che posso pensare di fare questo è il seguente, ma l'utilizzo di __makeref, FieldInfo.SetValueDirect o solo System.TypedReference in generale invalida la conformità CLS.Posso impostare un valore su una struttura attraverso il riflesso senza box?

// code illustrating the issue: 
TestFields fields = new TestFields { MaxValue = 1234 }; // test struct with one field 

FieldInfo info = fields.GetType().GetField("MaxValue"); // get the FieldInfo 

// actual magic, no boxing, not CLS compliant: 
TypedReference reference = __makeref(fields); 
info.SetValueDirect(reference, 4096); 

La controparte compliant di SetValueDirect è SetValue, ma ci vuole un oggetto come l'obiettivo, da qui la mia struct sarà inscatolato, rendendo l'impostazione di un valore su una copia, non la variabile originale me.

Una controparte generica per SetValue non esiste per quanto ne so. C'è un altro modo di impostare il campo di un (riferimento a una) struttura attraverso la riflessione?

risposta

5

Fai CLS involucro in SetValueDirect:

var item = new MyStruct { X = 10 }; 

    item.GetType().GetField("X").SetValueForValueType(ref item, 4); 


[CLSCompliant(true)] 
static class Hlp 
{ 
    public static void SetValueForValueType<T>(this FieldInfo field, ref T item, object value) where T : struct 
    { 
    field.SetValueDirect(__makeref(item), value); 
    } 
} 
+0

Forse non capisco CLSCompliancy, pensavo che significava che non si potevano _use_ le funzioni non conformi. Se questo è permesso, rende le cose molto più semplici. – Abel

+1

@Abel: CLS Compliant indica che i membri pubblici fanno riferimento solo ai tipi conformi a CLS. Non dice nulla su ciò che è contenuto nei tuoi membri. – Gabe

+0

Oops, la mia modifica di commento è andata persa. Sì, ho notato, è chiaro dai [primi tre elenchi puntati su MSDN su CLS Compliance] (http://msdn.microsoft.com/en-us/library/bhc3fa7f.aspx). Tuttavia mi sembra terribilmente strano che io abbia bisogno di una [parola chiave non documentata __makeref] (http://www.codeproject.com/Articles/38695/UnCommon-C-keywords-A-Look#makref) per farlo funzionare. – Abel

2

Non sicuro se questo si adatta ai vincoli, ma dichiarando l'istanza struct come ValueType, SetValue funzionerà come previsto.

ValueType fields = new TestFields { MaxValue = 1234 }; // test struct with one field 
    FieldInfo info = typeof(TestFields).GetField("MaxValue"); // get the FieldInfo 
    info.SetValue(fields, 4096); 
    Console.WriteLine(((TestFields)fields).MaxValue); // 4096 

Vedere this answer per ulteriori informazioni.

+1

+1: La tua risposta è sicuramente interessante, ma il codice non _non_ operano sul struct originale. Invece, è inscatolato per SetValue e unboxed perché lo si usa per scriverlo con WriteLine. Per vederlo da solo, controlla l'IL. – Abel

+0

Non ho guardato l'IL ma ho verificato che l'ultima riga mostra effettivamente 4096, quindi come può non funzionare sull'istanza originale? –

+0

OK, ora ho guardato l'IL. Non sono molto fluente, ma vedo le chiamate di cui stai parlando. Dalla sezione locali, sta dichiarando due istanze di TestField quando il C# ne ha solo uno. Molto strano. È la boxe che ti interessa o che il valore viene impostato correttamente? –

6

Per le proprietà, se avete il tipo struct e di proprietà, è possibile creare un delegato dal setter di proprietà. Come fai notare, i campi non hanno setter, ma è possibile crearne uno che si comporta esattamente lo stesso:

delegate void RefAction<T1, T2>(ref T1 arg1, T2 arg2); 

struct TestFields 
{ 
    public int MaxValue; 

    public int MaxValueProperty 
    { 
     get { return MaxValue; } 
     set { MaxValue = value; } 
    } 
}; 

static class Program 
{ 
    static void Main(string[] args) 
    { 
     var propertyInfo = typeof(TestFields).GetProperty("MaxValueProperty"); 
     var propertySetter = (RefAction<TestFields, int>)Delegate.CreateDelegate(typeof(RefAction<TestFields, int>), propertyInfo.GetSetMethod()); 

     var fieldInfo = typeof(TestFields).GetField("MaxValue"); 

     var dynamicMethod = new DynamicMethod(String.Empty, typeof(void), new Type[] { fieldInfo.ReflectedType.MakeByRefType(), fieldInfo.FieldType }, true); 
     var ilGenerator = dynamicMethod.GetILGenerator(); 
     ilGenerator.Emit(OpCodes.Ldarg_0); 
     ilGenerator.Emit(OpCodes.Ldarg_1); 
     ilGenerator.Emit(OpCodes.Stfld, fieldInfo); 
     ilGenerator.Emit(OpCodes.Ret); 
     var fieldSetter = (RefAction<TestFields, int>)dynamicMethod.CreateDelegate(typeof(RefAction<TestFields, int>)); 

     var fields = new TestFields { MaxValue = 1234 }; 
     propertySetter(ref fields, 5678); 
     fieldSetter(ref fields, 90); 
     Console.WriteLine(fields.MaxValue); 
    } 
} 
+0

Sì, è vero, ma la mia domanda riguarda i campi, non le proprietà. Leggera ma importante differenza: i campi non hanno un setter dedicato. – Abel

+0

Oops, giusto, scusa per questo, verrà modificato. – hvd

+0

@Abel Improvvisamente sembra meno elegante, ma funziona ancora con i campi. (Puoi memorizzare in cache i metodi creati se li usi molto.) – hvd

Problemi correlati