Archivio per la categoria "C#"



Estendere le classi builtin di IronRuby con nuovi metodi

In precedenza abbiamo visto come con IronRuby sia già possibile estendere a runtime le classi builtin sfruttando lo stesso Ruby e il suo concetto di classi aperte, tuttavia molti metodi che noteremo mancare in queste prime fasi di sviluppo, come MutableString#reverse usato per il nostro esempio ma anche molti altri , fanno parte del set di metodi "standard" delle classi builtin dell'interprete Ruby originale di Matz. L'ideale sarebbe quindi implementarli direttamente in IronRuby, guadagnandone anche in prestazioni, ma la sorpresa è che questo lavoro non è per nulla complicato.

Muoversi all'interno dell'archivio con i sorgenti è semplice, nella directory Docs è presente un readme.htm (tra l'altro realizzato con TiddlyWiki) con alcune indicazioni sulle linee guida per la scrittura del codice (SourceCodeLayout) e un paragrafo che fa al caso nostro: come aggiungere un metodo a una classe builtin (AddAMethod). In Src abbiamo la directory eMicrosoft.Scripting e Ruby, rispettivamente i sorgenti del DLR e di IronRuby. I file su cui andremo a lavorare sono contenuti in Src\Ruby\Builtins dove si trovano le implementazioni di tutte le classi builtin di IronRuby.

A questo punto apriamo il file Src\Ruby\Builtins\MutableString.cs, scriviamo il nostro metodo all'interno della classe MutableString e vediamo di commentarlo:

1 [RubyMethodAttribute("reverse", RubyMethodAttributes.PublicInstance)] 2 public static MutableString Reverse(MutableString str) { 3 if (str.Length <= 1) 4 return new MutableString(str); 5 6 char[] reversed = new char[str.Length]; 7 int len = str.Length - 1; 8 for (int i = 0; i <= len; i++) 9 reversed[i] = str[len - i]; 10 11 return new MutableString(new String(reversed)); 12 }
  • riga 1: per specificare il nome che verrà usato in Ruby per il metodo e la sua visibilità sono stati creati rispettivamente l'attributo RubyMethodAttribute e l'enum RubyMethodAttributes. Come spiegato nel documento allegato, il motivo per cui viene utilizzato RubyMethodAttribute è semplicemente dovuto al fatto che in Ruby possono esistere nomi di metodi non validi in C# come empy? oppure downcase!, inoltre in questo modo è possibile ricreare la moltitudine di operatori come <=> (comparazione) oppure =~ (corrispondenza).
  • riga 2: i metodi che andremo a creare sono statici poiché in realtà il DLR sfrutta una propria implementazione degli extension method di C# 3.0, in cui i metodi di estensione si presentano appunto come statici e il primo argomento è rappresentato da this per i metodi di istanza. In questo caso, il primo argomento sarà rappresentato dall'oggetto chiamante (è come se da Ruby arrivasse self).

Commentiamo rapidamente l'implementazione, saltando solo la parte che si occupa semplicemente di invertire la stringa:

  • riga 3-4: se la stringa è vuota oppure è formata da un solo carattere allora è inutile proseguire dal momento che non c'è molto da invertire, tuttavia ritorniamo ugualmente una nuova istanza di MutableString poiché questo è il comportamento di Ruby, basta osservare in IRB con l'interprete originale come ogni chiamata a String#reverse restituisca un oggetto con un object_id sempre differente:
    irb(main):001:0> "CIAO".reverse.object_id
    => 23652180
    irb(main):002:0> "CIAO".reverse.object_id
    => 23648140
    irb(main):003:0> "C".reverse.object_id
    => 23644340
    irb(main):004:0> "C".reverse.object_id
    => 23640540
    irb(main):005:0> "".reverse.object_id
    => 23636820
    irb(main):006:0> "".reverse.object_id
    => 23633100
  • riga 11: non ci sono costruttori di MutableString che prevedano un array di caratteri come parametro, per cui creiamo una istanza intermedia di String partendo dal nostro array di caratteri e creiamo da quella la nostra nuova istanza di MutableString. Avremmo potuto fare in altro modo ma facciamoci andar bene questa riga, soffermarsi a questo punto dei lavori sui dettagli implementativi per spaccare il byte è abbastanza inutile visto che molte parti saranno soggette a cambiamenti nel corso dello sviluppo di IronRuby e del DLR.

Per compilare il tutto correttamente in base alle nostre modifiche, seguiamo alcuni passi:

  1. Modificare il file Build.cmd cambiando la configurazione da modalità Release a Debug ( /p:Configuration=Debug ) e poi modificare il file Src\Ruby\Builtins\GenerateInitializers.cmd togliendo il ..\ iniziale (c'è un errore nei path).
  2. Eseguire Build.cmd. Questa prima compilazione genererà l'utility Bin\Debug\ClassInitGenerator.exe che lanceremo attraverso Src\Ruby\Builtins\GenerateInitializers.cmd e che (ri)genera automaticamente il file Src\Ruby\Builtins\Initializer.Generated.cs contenente i vari dispatcher ai metodi. Ogni volta che a una classe builtin aggiungiamo un metodo visibile da Ruby, dobbiamo rigenerare questo file (si può anche editare a mano, ma è ovviamente meno comodo e aumenta la possibilità di errore).
  3. Lanciare nuovamente Build.cmd per compilare il progetto con il nostro Initializer.Generated.cs aggiornato per riflettere l'aggiunta del metodo MutableString#reverse.

Non ci resta da fare altro che verificare i risultati tramite RBX, lanciandolo da Bin\Debug\rbx.exe:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".reverse.object_id
=> 43
>>> "CIAO".reverse.object_id
=> 44
>>> "C".reverse.object_id
=> 45
>>> "C".reverse.object_id
=> 46
>>> "".reverse.object_id
=> 47
>>> "".reverse.object_id
=> 48

La stringa viene correttamente invertita e il comportamento è il medesimo di quello riscontrato in IRB con l'interprete Ruby originale.

Impressioni e primi passi con IronRuby

Ormai si sta parlando in diversi lidi del rilascio dei sorgenti di una prima versione "parecchio alfa" (non per niente definita pre-alfa 1) di IronRuby tuttavia non c'è niente di meglio che esplorare e sperimentare in prima persona sia le potenzialità che il codice sorgente stesso di questa nuova implementazione di Ruby totalmente basata su .NET Framework. Trattandosi di una versione ampiamente preliminare è ovvio che non tutto funzioni come dovrebbe così come è ovvio che manchino molti pezzi, però personalmente trovo che il progetto sia già in uno stato relativamente più che ottimo: la maggior parte delle mancanze risiedono più in metodi non ancora implementati per i tipi base che altro, ma John Lam ha chiarito come lui e il suo team abbiano preferito concentrarsi maggiormente su AST e parser ma soprattutto sulla struttura del core language, dispatcher per gestire da subito i blocchi tipici di Ruby e interoperabilità con .NET. Credetemi però, avere già tutto questo funzionante dopo pochi mesi e con buone prestazioni nonostante la scarsa ottimizzazione messa in atto è un risultato non indifferente, soprattutto nel contesto di un linguaggio che purtroppo manca ancora oggi di specifiche scolpite sulla pietra e dove l'unico set di specifiche è rappresentato dall'MRI ufficiale, di cui però il team di IronRuby non può consultare i sorgenti per motivi prettamente legali. Per un'altra iniezione di ottimismo basta poi dare un'occhiata al sorgente stesso di IronRuby potendone già constatare pulizia e chiarezza, tanto che nel giro di pochi minuti è possibile avere un'idea di massima su come e dove andare a mettere le mani per cominciare ad estendere in prima persona l'implementazione. Per quanto mi riguarda ho l'impressione che IronRuby si evolverà in un progetto molto importante e mi sembra che ci sia anche un crescente entusiasmo da parte dei rubysti che al tempo stesso sono o sono stati sviluppatori .NET, cosa che mi fa ben sperare per una buona partecipazione al progetto da parte della comunità.

Ora però cominciamo a fare qualche esperimento, compilando prima di tutto i sorgenti di IronRuby. Fortunatamente non è necessario avere installato Visual Studio o altri ambienti di sviluppo, basta avere una normale installazione di .NET 2.0 (anche soltanto redist, non serve l'SDK) e un editor di testo/codice decente. All'interno dell'archivio compresso troverete il file Build.cmd che contiene già le impostazioni per msbuild.exe, molto probabilmente però dovrete specificarne all'interno il path completo sostituendo le variabili. Nel mio caso, Build.cmd è diventato:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe 
      /p:Configuration=Release /t:Rebuild IronRuby.sln

Per la cronaca IronRuby può essere compilato anche con Mono in Windows o Linux, anche se allo stato attuale è necessario sfruttare una build realizzata dai sorgenti nel repository SVN. La compilazione genererà tra le varie DLL anche l'eseguibile rbx.exe che è una sorta di incrocio tra ruby.exe (interprete) e IRB (console interattiva): eseguendolo senza alcun parametro RBX parte in modalità interattiva, in pieno stile IRB, altrimenti specificando un file/script in Ruby come parametro quest'ultimo verrà eseguito direttamente proprio come con l'interprete tradizionale. Per le nostre prime prove giocheremo un po' con la classe String. Anzi, per ora sarebbe meglio dire MutableString visto il seguente output da RBX:

>>> "CIAO".class
=> MutableString

L'output in IRB invece è:

irb(main):001:0> "CIAO".class
=> String

Come già esplicitamente detto da John Lam, questo comportamento verrà sistemato con i prossimi rilasci, tuttavia l'occasione ci permette di mettere in evidenza come una stringa in Ruby sia effettivamente mutabile dal momento che il suo contenuto può essere modificato senza necessariamente generare una nuova istanza. Proseguendo con le prove, si può incontrare fin da subito una testimonianza dell'incompletezza dei metodi per i tipi base:

>>> "CIAO".reverse
System.MissingMethodException: undefined local variable or method `reverse' for 
ciao:MutableString   in Ruby.Builtins.Kernel.MethodMissing(CodeContext context, Object self, 
Proc proc, SymbolId name, Object[] args) in j:\ir\pre-alpha1\Src\Ruby\Builtins\Kernel.cs:riga 169
[...]

La mancanza del metodo MutableString#reverse genera un'eccezione, tuttavia in Ruby potremmo intercettare la chiamata a un metodo non esistente sfruttando l'hook Object#method_missing che è chiamato automaticamente ogni volta che viene invocato un metodo non definito per un oggetto. Proviamo a sfruttare questo hook per vedere come si comporta IronRuby:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end end

Basta copiare e incollare il codice in RBX (proprio come faremmo in IRB) e poi chiamare un metodo non definito per un oggetto di tipo MutableString, come per esempio lo stesso reverse:

>>> "CIAO".reverse
Sorry, reverse does not belong to me.
=> nil

Grande, Object#method_missing c'è e funziona! Però a noi il metodo MutableString#reverse serve proprio, riusciremo a sfruttare già in questa versione di IronRuby il concetto di classi aperte implementandolo direttamente via codice Ruby? Proviamo ampliando il codice di esempio di poco fa:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end def reverse reversed = " " * self.length precalculated = self.length - 1 self.length.times do |i| reversed[i] = self[precalculated - i] end reversed end end

Come prima, incolliamo il codice in RBX e poi proviamo a invocare il metodo che abbiamo appena definito:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".my_undefined_method
Sorry, my_undefined_method does not belong to me.
=> nil

Funziona tutto, veramente ottimo! Ora però la faccenda comincia a farsi un po' più complicata perché vogliamo che il nostro metodo MutableString#reverse sia implementato "nativamente" in IronRuby, per motivi puramente prestazionali ma anche perché è giusto che sia così trattandosi di un metodo effettivamente appartenente alla classe String in Ruby. In pratica è giunto il momento di cominciare a giocare con i sorgenti in C# di IronRuby, anche se lo faremo in un messaggio successivo 🙂

Perché un delegate non è un constraint valido per i generic di C#

Durante la creazione di un helper per facilitarci la vita con LuaInterface nella creazione del delegate corretto per invocare funzioni definite in script Lua da C#, ho messo in evidenza il fatto che un delegate non costituisce un constraint valido per i generic e di conseguenza non viene accettato dal compilatore, che restituirà di conseguenza l'errore CS0702: Constraint cannot be special class 'System.Delegate'. La domanda è: perché? Ecco uno snippet ovviamente non compilabile ma che aiuta a comprendere il motivo di tale limitazione:

1 public class Test<T> where T : System.Delegate 2 { 3 public event T Event; 4 5 public void FireEvent() 6 { 7 if (Event != null) 8 Event(this, new EventArgs()); 9 } 10 }

In fase di compilazione la chiamata a un evento viene validata in base alla signature dell'evento stesso, tuttavia se fosse possibile specificare T come tipo delegate questa firma verrebbe a mancare rendendo impossibile la sua validazione ed ecco quindi l'eccezione che ha portato a considerare System.Delegate e System.MulticastDelegate (ma in realtà anche System.Enum) come delle classi speciali non utilizzabili come costraints. Pertanto, anche se può sembrare una fregatura, il motivo per questa scelta è piuttosto valido.

Invocare metodi Lua da C# con i Delegate

Nel precedente messaggio abbiamo visto come recuperare un oggetto LuaFunction, contenente il riferimento a una funzione definita in uno script Lua, sfruttando il metodo GetFunction. Lo stesso metodo però ha un overload abbastanza interessante che offre la possibilità di recuperare un delegate, con il vantaggio di poterci quindi integrare meglio all'interno del framework .NET. Vediamo un esempio:

1 using System; 2 using LuaInterface; 3 4 namespace NRK 5 { 6 public delegate double AddDelegate(double a, double b); 7 8 class Application 9 { 10 static void Main(string[] args) 11 { 12 Lua lua = new Lua(); 13 14 lua.DoString(@" 15 function add(x, y) 16 return x + y 17 end 18 "); 19 20 AddDelegate add = lua.GetFunction(typeof(AddDelegate), "add") as AddDelegate; 21 22 if (IsDefinedInLua(add)) 23 { 24 double res = add(5.3, 3.2); 25 Console.WriteLine(String.Format("Il risultato è: {0}", res)); 26 } 27 } 28 29 static bool IsDefinedInLua(Delegate delegateOfFunction) 30 { 31 if (delegateOfFunction.Target is LuaDelegate) 32 return (delegateOfFunction.Target as LuaDelegate).function != null; 33 34 return false; 35 } 36 } 37 }

Come al solito analizziamo il codice:

  • riga 6: definiamo il delegate che verrà utilizzato per invocare la nostra funzione in Lua. Purtroppo non è possibile definire il delegate utilizzando una lista variabile di argomenti dal momento che LuaInterface non lo supporta, quindi questo tipo di definizione non è valido e a runtime lancia un'eccezione:
    // non funzionerà a runtime... public delegate double AddDelegate(params double[] args);
  • riga 20: l'overload di GetFunction prende come parametri un System.Type per poter generare il delegate del tipo corretto e il nome della funzione in Lua da associargli. Come potete vedere la sintassi è un po' verbosa, usando i generics sarebbe possibile rimuovere almeno un AddDelegate e la chiamata a typeof, per fare ciò dovremmo definire un metodo di utilità come vedremo più avanti.
  • riga 22: il metodo IsDefinedInLua che qui abbiamo utilizzato e che alla riga 29 abbiamo definito è un helper che ci permette di capire se internamente il nostro delegate punta a una funzione Lua effettivamente definita oppure no, questo per evitare di incappare in una NullReferenceException nel caso nel codice Lua non sia esistente la funzione add. La mia impressione è che probabilmente sarebbe stato meglio se in LuaInterface avessero definito un tipo che identificasse chiaramente il puntamento a una funzione inesistente all'interno di una istanza di LuaDelegate (chessò, un tipo NullLuaFunction per esempio) anziché utilizzare direttamente null, in questo modo avrebbero evitato ai programmatori di incappare in eccezioni di questo tipo a runtime o di effettuare costantemente dei controlli per evitarle.

Facendo un passo indietro, prima abbiamo notato come l'overload di GetFunction utilizzato in questa occasione abbia in realtà una sintassi abbastanza pesante, vediamo quindi come ridurne la verbosità sfruttando i generics:

static T CreateDelegate<T>(Lua lua, String function) where T : class { Type delegateType = typeof(T); if (!delegateType.IsSubclassOf(typeof(Delegate))) throw new ArgumentException("T must be derived from System.Delegate"); return lua.GetFunction(delegateType, function) as T; } // Esempio di utilizzo: // AddDelegate add = CreateDelegate<AddDelegate>(lua, "add");

Integreremo più avanti questo metodo nel nostro codice, però possiamo già fare qualche considerazione:

  • purtroppo i delegate e le classi che ne derivano non possono essere usati come constraint per i generic. Può sembrare brutto ma in realtà esiste un buon motivo per questa limitazione, riprenderò l'argomento in un breve con un messaggio dedicato che vi illustro in questo messaggio.
  • il fatto che non si possano usare i delegate come constraint ci obbliga a fare un minimo di controllo giusto per essere sicuri e, nel caso, indicare la retta via, per cui controlliamo che il tipo T sia effettivamente derivante da System.Delegate e sia quindi consumabile da GetFunction.
  • usiamo class come constraint per poter utilizzare l'operatore as ed effettuare quindi un cast dal tipo base System.Delegate (resituito da GetFunction) al tipo specificato da T.
  • avendo a disposizione le novità di C# 3.0 sarebbe stato possibile utilizzare gli extension method per poter "attaccare" direttamente a LuaInterface.Lua il metodo generico CreateDelegate<T>, sarebbe stato sicuramente tutto molto più pulito.

Nei prossimi messaggi cominceremo a capire meglio l'utilità di utilizzare un delegate piuttosto che un oggetto LuaFunction per invocare i metodi definiti in Lua.

C# e Lua? Insieme? Rien de plus facile!

Qualche giorno fa vi ho parlato di Lua e di come configurarne correttamente l'interprete su Windows per poter fare qualche esperimento con questo linguaggio, quello che però non vi ho detto è che un valido motivo per quel messaggio introduttivo c'è e va oltre il semplice utilizzo nell'ambito dello scripting fine a se stesso. Del resto il punto di forza di Lua non risiede tanto nel suo utilizzo stand-alone, vista tra l'altro la scarsità di librerie a disposizione, quanto nell'estrema facilità e versatilità di integrazione dell'interprete all'interno di applicazioni scritte in altri linguaggi, in modo da poter offrire funzionalità di scripting o plugin in maniera facile e veloce nei propri progetti (come dimostrato per esempio in alcuni videogiochi). Tornando a noi, il motivo principale di questo interessamento è che in questo periodo ho l'occasione di poter sperimentare l'integrazione di Lua in un piccolo progetto scritto in C# grazie all'utilizzo dell'ottima libreria LuaInterface, perciò nei prossimi giorni cercherò di riportarvi qualche annotazione su questa esperienza attraverso qualche messaggio con un approccio particolarmente easy, non intendo scrivere un tutorial o simili.

Due note prima di cominciare: per questa serie di messaggi non è necessario seguire preventivamente le mie istruzioni sull'interprete Lua in Windows dato che esse servivano solamente per configurare l'interprete stesso in forma stand-alone (vi potrebbero tornare comunque utili nel momento in cui vorrete effettuare delle prove di codice Lua direttamente nel suo ambiente), inoltre io utilizzerò C# ma LuaInterface dovrebbe supportare tutti i linguaggi CLS-compliant che girano sul CLR.

L'unico ingrediente necessario per iniziare è l'ultima versione di LuaInterface e la nostra ricetta prevede che dall'archivio utilizziate i file LuaInterface.dll (il bridge vero e proprio da includere come reference nel progetto della vostra applicazione o da passare come argomento a CSC.EXE attraverso il parametro /r nel caso scriviate tutto con un editor di testo), luanet.dll e lua51.dll (rispettivamente la libreria che fa da collante tra il CLR e l'interprete Lua e la DLL dell'interprete Lua stesso, entrambi i file sono da copiare nella directory che conterrà il vostro programma compilato). A questo punto creiamo una piccolissima applicazione console per verificare che tutto funzioni correttamente...

1 using System; 2 using LuaInterface; 3 4 namespace NRK 5 { 6 class Application 7 { 8 static void Main(string[] args) 9 { 10 Lua luaEngine = new Lua(); 11 Console.WriteLine(luaEngine.GetType()); 12 } 13 } 14 }

Una volta compilato il programma, dovremmo ottenere come output in console la scritta LuaInterface.Lua. Da notare l'importazione del namespace LuaInterface alla riga 2 e la creazione dell'istanza dell'interprete Lua alla riga 10. La prova successiva: dare in pasto all'interprete del codice Lua da eseguire per ottenere un risultato. Per fare ciò sviluppiamo in Lua la classica funzione add...

1 using System; 2 using LuaInterface; 3 4 namespace NRK 5 { 6 class Application 7 { 8 static void Main(string[] args) 9 { 10 Lua luaEngine = new Lua(); 11 12 luaEngine.DoString(@" 13 function add(x, y) 14 return x + y 15 end 16 "); 17 18 LuaFunction luaFunction_Add = luaEngine.GetFunction("add"); 19 object[] ret = luaFunction_Add.Call(5.3, 3.2); 20 21 double result = Convert.ToDouble(ret[0]); 22 Console.WriteLine(String.Format("Il risultato è: {0}", result)); 23 } 24 } 25 }

Eseguendo il compilato dovremmo ottenere come risultato la somma di 5,3 e 3,2, con il seguente output in console: Il risultato è: 8,5. Ora analizziamo i punti di interesse di questo listato:

  • riga 12: il metodo DoString dell'istanza di Lua ci permette di dare in pasto all'interprete delle parti di codice Lua in forma di stringa. In questo caso abbiamo utilizzato questo metodo per definire in Lua la funzione add che restituirà la somma di due parametri.
  • riga 18: il metodo GetFunction dell'istanza di Lua consente di "importare" nel nostro programma una funzione definita in Lua, infatti in questo caso lo utilizziamo per ottenere il riferimento alla nostra funzione add nella forma di una istanza della classe LuaFunction.
  • riga 19: l'istanza di LuaFunction espone il metodo Call il quale consente di invocare la funzione in Lua a cui fa riferimento specificandone i parametri da passare e ottenendo come risultato un array di Object. In questo caso verrà restuito un array di un solo elemento contenente il nostro risultato.

Come potete vedere l'importazione dell'interprete Lua in un'applicazione scritta in C# con LuaInterface è un'operazione di una semplicità imbarazzante che ci permette di iniziare veramente da subito. Prossimamente approfondiremo l'argomento complicando sempre di più il nostro codice di esempio.