Nel framework 4.0 è stato aggiunto un insieme di classi che ci permettono di rendere il nostro codice e il .NET framework in generale “dinamico”. Cosa intendo per “dinamico”? Prendiamo ad esempio un semplice frammento di codice C#:
…
MyClass c = new MyClass();
c.Foo();
…
dal momento che questo codice compila possiamo affermare con sicurezza che la classe MyClass ha un metodo Foo() che viene invocato sull’istanza c. Questa assunzione la fa anche il compilatore che verifica a compile-time che quello che il nostro amato e “statico” C# esprime sia corretto. Quando dico statico intendo il fatto che se utilizziamo un linguaggio “statico”(come C# e VB) dobbiamo specificare il tipo dell’oggetto(MyClass) a design-time, ovvero indico con precisione al compilatore quale sarà il codice del metodo Foo() che dovrà essere eseguito a runtime. Nel nostro esempio dovrò eseguire il metodo Foo() definito nella classe MyClass sull’istanza riferita da c.
Il sistema dei tipi del .NET Framework è fatto da tipi “statici” nel senso che noi dichiariamo al momento della scrittura del codice il “tipo” che vogliamo utilizzare e il compilatore al momento della compilazione controlla che quello che abbiamo scritto sia “staticamente” corretto(che il tipo esista e abbia i vari membri che vogliamo utilizzare).
Con .NET 4.0 possiamo rendere il semplice frammento di codice dinamico, esempio(C#):
…
dynamic c = MyClass();
c.Foo();
…
specificando la keyword dynamic indichiamo al complilatore che il riferimento c punta ad un oggetto “dinamico” nel senso che al momento della compilazione non viene controllato se Foo() esiste, ma a “runtime” l’infrastruttura DLR(Dynamic Language Runtime) cerca il membro Foo() e lo esegue, sollevando un’eccezione se il membro non viene trovato. Questo significa che quando dichiariamo un tipo dynamic il compilatore attraverso il nuovo insieme di classi(System.Dynamic namespace) crea i “metadati” che serviranno a runtime per “risolvere” i membri. Diciamo che è come avere tutti membri di una classe virtuali, ma la risoluzione viene fatta a runtime da una “logica” custom(questa è una mia libera interpretazione).
Chiaramente possiamo noi stessi creare dei tipi dinamici, ereditando da una classe System.Dynamic.DynamicObject e facendo l’override di alcuni metodi.
Vediamo un esempio reale. Mi capita spesso di dover lavorare con file Xml attraverso la classe XElement. Dato il seguente file Xml:
<?xml version="1.0" encoding="utf-8" ?>
<Anagrafiche>
<Anagrafica id="1">
<Nome>Marco</Nome>
<Cognome>Rossignoli</Cognome>
<Contatti>
<Cells>
<Cell tipo="vodafone">+1234567890</Cell>
<Cell tipo="wind">+0987654321</Cell>
</Cells>
<Emails>
<Email>nospam@nospam.no</Email>
<Email>nospam2@nospam.no</Email>
<Email>nospam3@nospam.no</Email>
</Emails>
</Contatti>
</Anagrafica>
<Anagrafica id="2">
<Nome>Mario</Nome>
<Cognome>Rossi</Cognome>
<Contatti>
<Cells>
<Cell tipo="vodafone">+6789012345</Cell>
<Cell tipo="3">+6543217890</Cell>
</Cells>
<Emails>
<Email>nospam4@nospam.no</Email>
<Email>nospam5@nospam.no</Email>
<Email>nospam6@nospam.no</Email>
</Emails>
</Contatti>
</Anagrafica>
</Anagrafiche>
devo leggere i vari elementi e attributi e stamparli in console. Posso utilizzare le classiche API della classe XElement in questo modo:
XElement el = XElement.Parse(File.ReadAllText("Anagrafica.xml"));
foreach (var anagraficaElement in el.Descendants("Anagrafica"))
{
Console.WriteLine("id:{0}", anagraficaElement.Attribute("id").Value);
Console.WriteLine("Nome:{0}", anagraficaElement.Element("Nome").Value);
Console.WriteLine("Cognome:{0}", anagraficaElement.Element("Cognome").Value);
Console.WriteLine("Contatti");
foreach (var cell in anagraficaElement.Element("Contatti").Descendants("Cell"))
{
Console.WriteLine("Cellulare:{0} tipo:{1}", cell.Value, cell.Attribute("tipo").Value);
}
foreach (var email in anagraficaElement.Element("Contatti").Descendants("Email"))
{
Console.WriteLine("Email:{0}", email.Value);
}
Console.WriteLine("-----");
}
come possiamo vedere dal codice, per poter leggere gli attributi e gli elementi devo conoscere il funzionamento dell’Xml e delle API di XElement. Non sarebbe più comodo se potessi scrivere lo stesso codice in questo modo?
foreach (var anagrafica in el.Anagrafiche)
{
Console.WriteLine("id:{0}", anagrafica.id);
Console.WriteLine("Nome:{0}", anagrafica.Nome);
Console.WriteLine("Cognome:{0}", anagrafica.Cognome);
Console.WriteLine("Contatti");
foreach (var contatti in anagrafica.Contatti)
{
foreach (var cellulari in contatti.Cells)
{
Console.WriteLine("Cellulare:{0} tipo:{1}", cellulari.Cell, cellulari.tipo);
}
foreach (var email in contatti.Emails)
{
Console.WriteLine("Email:{0}", email.Email);
}
}
Console.WriteLine("-----");
}
La “nuova” sintassi è chiaramente più chiara e molto più agnostica ai vari concetti dell’xml(elementi, attributi etc…) e di XElement, l’unica cosa che dobbiamo fare è “leggere” l’xml e richiedere l’elemento/attributo che ci interessa, qualcuno a runtime saprà come farlo.
Il DLR ci permette di realizzare questa “magia”, ovvero tutto quello che dobbiamo fare è rendere “dinamici” gli oggetti che devono “risolvere a runtime” i vari membri. Nel nostro esempio sono i riferimenti in grassetto. Cerco di spiegarmi meglio, l’idea è demandare ad un nostro pezzo di codice l’effettiva risoluzione delle varie proprietà richieste, come ad esempio l’id di anagrafica, il nome di anagrafica, il Cell di cellulari etc…il codice completo risulterà così:
XElement doc = XElement.Parse(File.ReadAllText("Anagrafica.xml"));
dynamic el = doc.AsDynamic();
foreach (var anagrafica in el.Anagrafiche)
{
Console.WriteLine("id:{0}", anagrafica.id);
Console.WriteLine("Nome:{0}", anagrafica.Nome);
Console.WriteLine("Cognome:{0}", anagrafica.Cognome);
Console.WriteLine("Contatti");
foreach (var contatti in anagrafica.Contatti)
{
foreach (var cellulari in contatti.Cells)
{
Console.WriteLine("Cellulare:{0} tipo:{1}", cellulari.Cell, cellulari.tipo);
}
foreach (var email in contatti.Emails)
{
Console.WriteLine("Email:{0}", email.Email);
}
}
Console.WriteLine("-----");
}
semplicemente ci serve un modo per avere un “wrapper” dynamic sul nostro XElement, sarà la logica che mettiamo nel nostro DynamicObject a cercare attraverso le varie API XElement l’elemento/attributo richiesto. Nella MIA implementazione ho usato un’extension method su XElement per eleganza, ma non è obbligatorio in quanto come vedremo tra un secondo la definizione del nostro dynamic object è una normale classe che eredita da System.Dynamic.DynamicObject e ha un costruttore al quale possiamo passare l’istanza di XElement.
E ora vediamo la definizione della classe “magica”:
public static class DynamicXmlExtension
{
public static dynamic AsDynamic(this XElement xml)
{
return new DynamicXml(xml);
}
public class DynamicXml : DynamicObject
{
public DynamicXml(XElement element)
{
_element = element;
}
private bool TryGetMemberLogic(GetMemberBinder binder, out object result)
{
….logica interna di ricerca degli elementi/attributi che nascondo volontariamente
in quanto incompleta, non testata e a scopo dimostrativo…alla fine non fa
altro che utilizzare binder.Name con le API XElement
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return TryGetMemberLogic(binder, out result);
}
public override string ToString()
{
return _element.ToString();
}
}
}
la classe contiene 2 cose fondamentali: la prima è l’extension method che ritorna l’oggetto di tipo dynamic e la seconda è l’implementazione del nostro tipo DynamicXml che eredita da System.Dinamic.DynamicObject. Il segreto per realizzare la “magia” sta nel ridefinire dei metodi della classe base DynamicObject. Abbiamo un bel pò di metodi che ci permettono di gestire la risoluzione di praticamente qualsiasi membo di una classe(ma non solo). Nel nostro esempio quello che ho dovuto ridefinire è il Get di un membro(ricodate anagrafica.Nome), quindi ho fatto l’override di TryGetMember(come possiamo notare tutti i metodi cominciano con TryXXX). TryGetMember mi fornisce 2 parametri, il primo GetMemberBinder attraverso la proprietà Name mi comunica il nome del membro richiesto(ad esempio in caso di anagrafica.Nome binder.Name sarà Nome, GetMemeberBinder contiene altre interessanti proprietà che non tratterò per semplicità) il secondo è una variabile di out che la mia logica di ricerca deve assegnare. Insomma in soldoni cerco il binder.Name nel mio xml attraverso le API XElement e quando trovo l’elemento lo assegno a result. I parametri forniti da questi metodi variano a seconda della caratteristica che stiamo cercando di risolvere in modo “dinamico” a runtime(ogni TryXXX ha i suoi parametri tipizzati).
Carino o no?
Il DLR è un framework molto potente ed interessante, oltre a fare queste “magie” viene impiegato per risolvere tanti altri problemi, per una trattazione approfondita potete partire da qua Dynamic Language Runtime Overview.
Per scaricare una demo funzionante clicca
qua
Attenzione non utilizzate il codice in produzione, non è completo non è testato ed è a scopo dimostrativo; inoltre mi sono inventato una mia logica di ricerca che può non piacere o risultare scomoda…ereditate e implementate la vostra ;-)