LINQ to reflection - Parte due

di , in .NET 3.5,

Qualche giorno fa ho parlato di come è possibile generare IL a runtime per velocizzare le operazioni che solitamente si effettuano via reflection.

Oltre alla FastCreateInstance ho implementato una FastSetValue. In questo caso il metodo dinamico generato non fa altro che ricevere l'istanza e il valore impostandola sulla proprietà. Ecco il codice dell'extension method:

public static void FastSetValue(this PropertyInfo property, object obj, object value)
{
    if (property == null)
        throw new ArgumentNullException("property");
    SetValueInvoker invoker = (SetValueInvoker)setValueInvokers[property];
    if (invoker == null)
    {
        DynamicMethod method = new DynamicMethod(property.Name, null, new Type[] { typeof(object), typeof(object) }, typeof(object), true);
        ILGenerator il = method.GetILGenerator();
        // ((Tipo)oggetto).Proprieta = valore
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Castclass, property.DeclaringType);
        il.Emit(OpCodes.Ldarg_1);
        if (property.PropertyType.IsClass)
            il.Emit(OpCodes.Castclass, property.PropertyType);
        else
            il.Emit(OpCodes.Unbox_Any, property.PropertyType);
        il.EmitCall(OpCodes.Callvirt, property.GetSetMethod(true), null);
        il.Emit(OpCodes.Nop);
        il.Emit(OpCodes.Ret);
        invoker = (SetValueInvoker)method.CreateDelegate(typeof(SetValueInvoker));
        setValueInvokers[property] = invoker;
    }
    invoker(obj, value);
}

La tecnica è la stessa del FastCreateInstance. Un Hashtable mantiene il delegate specifico per la PropertyInfo e se non c'è crea il codice IL. In questo caso carica nello stack il primo argomento (l'entità) e ne effettua il cast sul tipo. Poi carica il secondo argomento (il valore da impostare sulla proprietà) e ne effettua il cast se è un tipo di riferimento o l'unboxing se è un tipo di valore (numeri, date, ecc). A questo punto chiama il metodo set_nomeproprietà (le proprietà in .NET non sono altro che metodi con una nomenclatura particolare) che preleva dallo stack i due argomenti inseriti.

Una volta pronto il delegate SetValueInvoker(object obj, object value), poi lo chiamiamo con i parametri ricevuti. Ed ecco il suo utilizzo:

PropertyInfo pi = typeof(Product).GetProperty("Description", BindingFlags.Public | BindingFlags.Instance);
Product p = new Product();
pi.FastSetValue(p, "ciao");

Il risultato è che siamo passati da un'operazione più lenta 150 volte (la normale SetValue della PropertyInfo) ad un 15 volte più lento e anche in questo caso a causa del costo di ricerca nell'Hashtable e di creazione del metodo alla prima chiamata.

Una nota interessante su OpCodes.Castclass. In realtà questa operazione (obbligatoria quando scriviamo codice in C#) non è indispensabile poiché effettua solo un controllo sul tipo di puntatore presente nello stack. Se omesso, anche se sbagliamo il tipo, il CLR comunque va ad inserire un valore scorretto. Potremmo perfino inserire un numero in una stringa e tutto andrebbe senza problemi fino a quando non chiamiamo un metodo di istanza che fa saltare per aria il CLR :-). Ho preferito comunque lasciare il cast poiché non voglio fidarmi ciecamente del chiamante ed inoltre il costo di tale operazione è minimo e trascurabile.

Commenti

Visualizza/aggiungi commenti

LINQ to reflection - Parte due
| Condividi su: Twitter, Facebook, LinkedIn, Google+

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese