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:
- Lambdas
- Overengineering
- Lunedì quiz 4–soluzione
- L’Heisenbug perfetto
- Tor exit node lookup in ASP.Net


Facebook,
Wikio,
Segnalo.

giovedì 29 novembre 2007 alle 6:47 PM -
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?!
Permalink - Rispondi al commento
giovedì 29 novembre 2007 alle 7:03 PM -
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
Permalink - Rispondi al commento
giovedì 29 novembre 2007 alle 9:21 PM -
> simile ai costrutti "Eval" di
> alcuni linguaggi (è passato
> troppo tempo per ricordarmi quali!)
... JavaScript! (che però è interpretato)
Permalink - Rispondi al commento
venerdì 30 novembre 2007 alle 12:40 PM -
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.
Permalink - Rispondi al commento
venerdì 30 novembre 2007 alle 12:41 PM -
PS: debuggare un programma che contiene costrutti simili equivale a tirarsi una martellata sui gioielli di famiglia secondo me.
Permalink - Rispondi al commento
sabato 1 dicembre 2007 alle 11:43 AM -
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?
Permalink - Rispondi al commento
sabato 1 dicembre 2007 alle 6:14 PM -
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
), 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?
Permalink - Rispondi al commento
sabato 1 dicembre 2007 alle 9:19 PM -
@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.
Permalink - Rispondi al commento
martedì 12 gennaio 2010 alle 12:06 PM -
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.
Permalink - Rispondi al commento