Eccovi LINQ to reflection

di Cristian Civera, in .NET 3.5,

Un bel titolo ingannevole per attirarvi a leggere alcuni extension method che ho sviluppato per risolvere in parte le problematiche di performance delle quali ho discusso qua.

Come avevo accennato, un'opzione può essere quella di generare codice dinamico. Per farlo facilmente LINQ include un namespace System.Linq.Expressions dove possiamo rappresentare un'espressione con un modello ad oggetti. Questa classi sono vitali per LINQ to SQL e simili, ma la cosa carina è che la LambdaExpression permette di compilare l'espressione e ottenere un delegate ad essa. In particolare Expression<TDelegate> ci consente di specificare qualsiasi espressione con uno specifico delegate direttamente da codice per poi ottenerne il puntatore con il metodo Compile. Per esempio:

Expression<Func<Product>> ef = () => new Product();
Func<Product> f = ef.Compile();
Product p = f();

Il codice precedente rapprenta prima l'espressione per creare una nuova istanza di Product, poi la compila per avere una funzione vera e propria che non vuole nessun parametro, ma il cui tipo di ritorno è Product. Una cosa importantissima è la prima riga: poiché vogliamo un Expression il compilatore genera codice per rappresentare la lamba expression, diversamente dal normale uso che si fa con LINQ to Object, dove viene effettivamente creato un metodo che contiene la lambda.

Quindi questa introduzione per dire che possiamo usare questa caratteristica per generare al volo un metodo che ci permetta di istanziare velocemente un tipo. Ecco quindi l'extension method che ho sviluppato:

public static object FastCreateInstance(this Type type)
{
    if (type == null)
        throw new ArgumentNullException("type");
    CreateInstanceInvoker invoker = (CreateInstanceInvoker)createInstanceInvokers[type];
    if (invoker == null)
    {
        LambdaExpression e = Expression.Lambda(typeof(CreateInstanceInvoker), Expression.New(type), null);
        invoker = (CreateInstanceInvoker)e.Compile();
        createInstanceInvokers[type] = invoker;
    }
    return invoker();
}

In pratica con un Hashtable mantengo una lista di delegate in funzione del tipo e se non c'è, creo una LambdaExpression, la compilo per ottenere il delegate che userò le successive volte. L'utilizzo del metodo poi è semplicissimo:

Product p = typeof(Product).FastCreateInstance();

Usando questa tecnica i tempi si dimezzano rispetto all'uso di Activator.CreateInstance, seppure mai veloce abbastanza come usare il costruttore direttamente. Il costo dell'operazioni infatti si sposta e in questo caso è dovuto più al recupero del delegate in modo thread safe dall'Hashtable.

Cosa fa il metodo Compile? Utilizza la classe DynamicMethod che consente di generare per l'appunto un metodo dinamico, mediante IL per poi darlo in pasto al jitter e renderlo così veloce quanto il normale codice. Questa classe esiste dal .NET Framework 2.0 e facilita la definizione di Assembly, Module e Type dinamici, già possibile fin dalla versione 1.0. L'unico inconveniente è che bisogna masticare un po' di IL, perciò il metodo FastCreateInstance può creare il delegate anche in questo modo:

DynamicMethod method = new DynamicMethod("method", typeof(object), new Type[0], typeof(object), true);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
il.Emit(OpCodes.Ret);
invoker = (CreateInstanceInvoker)method.CreateDelegate(typeof(CreateInstanceInvoker));

Definisce un metodo senza parametri, ma con tipo di ritorno Object, ed emette due istruzioni: la newobj che crea l'istanza del tipo e mette nello stack il puntatore, mentre ret lo estrae dallo stack e lo restituisce al chiamante.

Ho trovato interessante questi internal perché piuttosto importanti e perché fanno capire meglio LINQ. Nel prossimo post mostrerò l'implementazione di FastSetValue e un FastSetValues che permette di valorizzare più proprietà contemporaneamente guadagnando moltissimo in prestazioni, ovviamente tutto direttamente in IL :-)-

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

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