Info:

twitter

Ultimi commenti: Comment feed

Tags:

Sponsor:

Archivio 2018:

Giu Mag Feb Gen

Archivio 2017:

Dic Nov Ott Mag Apr Mar Feb Gen

Archivio 2016:

Dic Nov Ott Ago Mag Mar Feb Gen

Archivio 2015:

Nov Ott Set Mar Gen

Archivio 2014:

Dic Nov Ott Set Lug Giu Mag Apr Gen

Archivio 2013:

Dic Nov Set Ago Lug Giu Mag Apr Feb Gen

Archivio 2012:

Dic Nov Ott Set Ago Giu Mag Apr Mar Feb Gen

Archivio 2011:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Archivio 2010:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Archivio 2009:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Archivio 2008:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Archivio 2007:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Archivio 2006:

Dic Nov Ott Set Ago Lug Giu Mag Apr Mar Feb Gen

Metodi dinamici di delegazione

Questi giorni sto giochicchiando con i DynamicMethod di .Net e ho pensato di condividere. Lo scopo di tali classi - conosciute anche come lightweight code generation - è di generare codice IL (il P-code di .Net) dinamicamente. Accoppiandoli con i metodi delegate (puntatori a funzione) si possono fare cose veramente interessanti. Gli esempi che seguono sono alquanto banali (il classico "Hello World"), ma con la LCG si possono fare cose moooolto deliziose: esempio 1, esempio 2.

Altrettanto interessante è il funzionamento della Virtual Machine stack based di .Net: il set di istruzioni è documentato qui ma se si vuole giocare seriamente a questo tipo di giochi pericolosissimi (roba impossibile da debuggare capace di creare codice a fungo atomico in men che non si dica) consiglio _IL_ testo di riferimento che è "Compiling for the .Net Language Runtime" il cui unico neo è quello di essere basato sulla CLR 1.0 (niente template e altre corbellerie "moderne"). Volendo, la via più breve per capire l'IL da generare, è l'uso "a posteriori" di reflector.

Ed ora gli esempi, giusto per stimolare l'appetito:

using System;
using System.Reflection;
using System.Reflection.Emit;

public class TestClass
{
    string message;
    public delegate void TestMethod();
    TestMethod myTestMethod;

    public TestMethod MyTestMethod
    {
        get { return myTestMethod; }
    }

    public TestClass(string aMessage)
    {
        this.message = aMessage;
    }

    public void RealTestMethod()
    {
        Console.WriteLine(this.message);
    }

    public void CreateMethod()
    {
        /*
         * this is equivalent to:
         * Console.WriteLine("Hello world!");
         * there is no access to object members 
         * so the method can be declared as 'static'
         * 
         */
          
        // declare the method signature as void HelloWorld()
        //
        DynamicMethod dm = 
            new DynamicMethod("HelloWorld",  // name of the method
            typeof(void),           // return type
            new Type[] { },         // input types
            typeof(TestClass)); // class to 'attach the method to
        ILGenerator il = dm.GetILGenerator();

        // create a constanst string using the 
        // message and put it on the stack
        il.Emit(OpCodes.Ldstr, this.message);

        // invoke Console.WriteLine(string)
        // since it's a static method it only 
        // requires a string (the one on the stack)
        il.Emit(OpCodes.Call, 
            typeof(Console).GetMethod("WriteLine", 
            new Type[] { typeof(string) }));

        // return
        il.Emit(OpCodes.Ret);

        // create a delegate
        this.myTestMethod = 
            (TestMethod) dm.CreateDelegate(
            typeof(TestMethod)
            );
    }


    public void CreateMethod2()
    {
        /*
         * this is equivalent to:
         * Console.WriteLine(this.message);
         * there is access to object members 
         * so the method cannot be declared as 'static'
         * 
         */

        // declare the method signature as 
        // void HelloWorld(TestClass implicitThis)
        // it will be mapped to 
        // TestClass.HelloWorld() as instance method
        //
        DynamicMethod dm = new DynamicMethod(
            "HelloWorld",                      // name of the method
            typeof(void),                      // return type
            new Type[] { typeof(TestClass) },  // input types
            typeof(TestClass));     // class to 'attach' the method to
        ILGenerator il = dm.GetILGenerator();

        // puts the arg_o (this) on the stack
        il.Emit(OpCodes.Ldarg_0);

        // puts (POP)->message on the stack
        il.Emit(OpCodes.Ldfld, typeof(TestClass).GetField("message", 
            BindingFlags.NonPublic | BindingFlags.Instance));

        // invoke Console.WriteLine(string)
        // since it's a static method it only 
        // requires a string (the one on the stack)
        il.Emit(OpCodes.Call, 
            typeof(Console).GetMethod(
                "WriteLine", new Type[] { typeof(string) }
                )
            );

        // return
        il.Emit(OpCodes.Ret);

        // create a delegate
        this.myTestMethod = 
            (TestMethod)dm.CreateDelegate(typeof(TestMethod), this);
    }
}


public class HelloWorld
{
 
    public static void Main(string[] args)
    {
        TestClass myTestClass = new TestClass("Hello World!");
        myTestClass.CreateMethod();
        myTestClass.MyTestMethod();

        myTestClass.CreateMethod2();
        myTestClass.MyTestMethod();
    }

}

-quack

Potrebbero interessarti anche:
Commenti (9):
1. sirus
giovedì 29 novembre 2007 alle 6:47 PM - unknown unknown unknown
   

Se ho capito bene (da quel poco che so del C#), in questo modo è possibile creare dei metodi a runtime ed associarli ad un delegato.

Potrebbe essere qualche cosa di simile alla funzionalità del Python che permette di aggiungere metodi (ed anche attributi) ad un oggetto durante la fase di runtime.

Sbaglio?!

   
2. Paperino
giovedì 29 novembre 2007 alle 7:03 PM - unknown unknown unknown
   

non sbagli: molto simile e forse più potente! Non conosco Python (è interpretato o compilato?) ma immagino che il metodo tu possa "associarlo" a runtime? Con i DynamicMethods puoi "costruirlo" on the fly, simile ai costrutti "Eval" di alcuni linguaggi (è passato troppo tempo per ricordarmi quali!).

La pericolosità sta nel fatto che si by-passa il compilatore e tutta una serie di controlli sui tipi fatti da questo: per un baco ho passato un tipo anziché un altro e neanche il debugger mi è stato di aiuto in quanto mi mostrava il tipo "corretto". L'ho capito solo usando la reflection. Però quando tutto funziona si possono ottenere prestazioni esagerate Big Smile

   
3. ilSilente
giovedì 29 novembre 2007 alle 9:21 PM - unknown unknown unknown
   

> simile ai costrutti "Eval" di

> alcuni linguaggi (è passato

> troppo tempo per ricordarmi quali!)

... JavaScript! (che però è interpretato)

   
4. sirus
venerdì 30 novembre 2007 alle 12:40 PM - unknown unknown unknown
   

Python è interpretato tuttavia in questa situazione non ha le stesse potenzialità del C# perché i metodi che vengono aggiunti a runtime non possono essere creati a runtime ma devono essere scritti prima di mandare in run il programma.

Supponendo di avere un metodo f() in una classe, possiamo associare ad f() il metodo a() o il metodo b() durante il runtime, tuttavia a() e b() devono essere stati definiti in precedenza.

   
5. sirus
venerdì 30 novembre 2007 alle 12:41 PM - unknown unknown unknown
   

PS: debuggare un programma che contiene costrutti simili equivale a tirarsi una martellata sui gioielli di famiglia secondo me. :D

   
6. wisher
sabato 1 dicembre 2007 alle 11:43 AM - unknown unknown unknown
   

Molto interessante come sia stato reso il più pulito possibile l'utilizzo di metodi dinamici, cosa che di per sé non è il massimo in termini di pulizia.

Per quanto riguarda le prestazioni (che tu definisci "esagerate"), il guadagno che si ottiene è così grande da giustificare inconvenienti come un debug a dir poco difficoltoso?

   
7. Blackstorm
sabato 1 dicembre 2007 alle 6:14 PM - unknown unknown unknown
   

Ma soprattutto, sono l'unico a trovare folle zompare a piè pari il controllo di tipo del compilatore su questi metodi dinamici? A parte che il debug, come dice siurus è una martellata sui "vunnerabili" (cit. e se indovinate chi è vi stimo Smile), perchè se non ho un controllo a compile time sulla coerenza dei tipi posso allegramente dare le craniate a ricercare il tipo sbagliato, o quale metodo mi dà problemi (considerando la complessità di questo breve pezzo di codice, per un qualcosa di un filo più complesso non oso immaginare cosa devo metterci nel codice)... Ma tutto questo a parte, oltre alla domanda di wisher, ne ho un'altra direttamente collegata: quali sono le applicazioni che effettivamente beneficiano di un metodo creato a runtime piuttosto che di uno dinamico a compile time? Perchè ad esempio, per gestire la classicissima rubrica (si, lo so, ma è l'esempio che conta [:-P]), credo che aggiunga complessità ma non efficineza. Potrei pensare a routine di intelligenza artificiale, o simili, che potrebbero beneficiare, ma considerata la già elavata complessità di questi software, non si rischia di avere un codice assolutamente instabile e dal debug pesantissimo?

   
8. Paperino
sabato 1 dicembre 2007 alle 9:19 PM - unknown unknown unknown
   

@wisher:

l'applicazione più interessante dei metodi dinamici è quella di sostituire l'uso della Reflection. Nel mio piccolo prototipo (mi son creato un ORM) il guadagno  è stato drammatico: si parla di 5x-6x.

@blackstorm:

generare codice è lo stesso tipo di lavoro che fa un compilatore. Generarlo al volo rende la cosa più difficile, ma fattibile. La generazione di codice è infatti usata - sin dai tempi del .Net framework 1.0 - in quei casi in cui i vantaggi della compilazione hanno senso (valutazione delle espressioni a runtime o operazioni di bulk-upload): esempi reali sono la compilazione delle RegEx (opzionale) o la generazione del codice di Serializzazione/Deserializzazione XML. Con .Net 1.x era tutto più complicato in quanto l'unità minima di generazione era la classe; con il .Net 2.0 le cose sono diventate più semplici, infatti si possono generare semplicemente metodi.

Sicuramente è una tecnica da usare con molta discrezione e solo se ci si può permettere di fare a meno dei controlli di compilazione.

   
9. Phenix
martedì 12 gennaio 2010 alle 12:06 PM - IE 8.0 Windows 7
   

// puts (POP)->message on the stack

non dovrebbe essere PUSH? o non ho capito niente io?

Inoltre, vediamo se ho capito (sono nuovo nel mondo C#). CreateMethod e CreateMethod2 differiscono nel modo in cui caricano nello stack il valore di message. Nel primo caso si va a pescare this.message e lo si carica nello stack come costante; nel secondo caso si carica prima il riferimento this e poi si carica nello stack il membro di nome "message" di ciò che è puntato da this.

   
Lascia un commento:
Commento: (clicca su questo link per gli smiley supportati; regole di ingaggio per i commenti)
(opzionale, per il Gravatar)