A44. Reflection, Parte I
Con il termine generale di reflection si intendono tutte le classi del Framework che permettono di accedere o manipolare assembly e moduli.
Utilizzando questa potenzialità, è possibile enumerare tutti i metodi, gli eventi, le proprietà, i campi e i tipi che una classe espone,
oppure creare dinamicamente nuovi oggetti da assembly differenti, oppure ancora ispezionare la signature di un metodo e il suo corpo,
calcolando anche quanta memoria potrebbe utilizzare. Da questi esempi si capisce che la reflection costituisce uno strumento davvero potente,
che infatti viene impiegato dallo stesso framework, "dietro le quinte", ad esempio nel processo di serializzazione (per maggiori informazioni
sulla serializzazione, vedere capitolo 81) o quando viene eseguito un late binding.
L'unico namespcae dedicato alla reflection è System.Reflection, che espone tutti i metodi e i tipi necessari; il namespace nidificato Emit permette invece la referenziazione dinamica di assembly e la creazione in tempo reale di nuovi oggetti provenienti da quegli assembly. L'unico tipo che prende parte alla reflection ma che non appartiene al namespace dedicato è System.Type, che rappresenta genericamente un tipo ed espone i metodi essenziali per ispezionarlo.
Ci sono svariati modi per caricare un assembly:
Ogni sistema operativo utilizza un meccanismo adatto per separare virtualmente ogni singolo programma, ond'evitare che parte del codice di uno possa influenzarne un altro, causando così un crash o ancora peggio, gravi danni al sistema. Su Windows, tale meccanismo viene implementato per mezzo dei processi, ognuno dei quali racchiude al suo interno uno e un solo programma, con le relative risorse e variabili. Infatti queste ultime vengono allocate in memoria usando indirizzi specifici per un solo processo e che quindi non possono venire lette da alcun altro processo. L'ambiente .Net fa in modo che ogni applicazione venga eseguita in un dominio applicativo, o AppDomain (o contesto di esecuzione) che risiede all'interno di un processo: per questo motivo è possibile creare più AppDomain che sono parte di uno stesso processo, ma che godono dello stesso isolamento e quindi della stessa sicurezza, poichè nessun assembly appartenente a un AppDomain può in alcun modo modificare un altro assembly che non sia nello stesso AppDomain. Il vantaggio di questa suddivisione è che AppDomain nello stesso processo possono comunicare in modo molto più efficiente rispetto ad AppDomain in processi diversi. Ad esempio, avrete notato che se si utilizzano due finestre distinte di Mozilla FireFox, c'è un unico processo associato, come nelle immagini seguenti:


È possibile usufruire di alcuni eventi di AppDomain inerenti al caricamento degli assembly, ma lo vedremo in seguito.
Una volta ottenuto un riferimento valido ad un assembly, è possibile analizzarlo richiamando i suoi membri, tra i quali i più significativi che analizzeremo ora sono:
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.
Binding
Per chi non sapesse in cosa consiste il processo di binding, lo spiego in modo veloce. L'azione del legare (in inglese, appunto, "bind") un
identificatore a un valore viene detta binding: si esegue un binding, ad esempio, quando si assegna un nome a una variabile.
Questo consente un'astrazione fondamentale affinchè il programmatore possa comprendere ciò che sta scritto nel codice: nessuno riuscirebbe
a capire alcunchè se invece dei nomi di variabili ci fossero degli indirizzi di memoria, o al posto dei tipi dei codici sconosciuti. Ebbene,
esistono due tipi di binding: quello statico o "early", e quello dinamico o "late". Il primo viene effetuato prima che il programma sia
eseguito, ed è quello che permette all'IntelliSense di funzionare. Il secondo, invece, viene portato a termine mentre il programma è in
esecuzione: ad esempio, richiamare dei metodi d'istanza di una classe Person da un oggetto Object è un esempio di late binding, poichè solo
a run-time, il nome del membro verrà letto, verificato, e, in caso di successo, richiamato.L'unico namespcae dedicato alla reflection è System.Reflection, che espone tutti i metodi e i tipi necessari; il namespace nidificato Emit permette invece la referenziazione dinamica di assembly e la creazione in tempo reale di nuovi oggetti provenienti da quegli assembly. L'unico tipo che prende parte alla reflection ma che non appartiene al namespace dedicato è System.Type, che rappresenta genericamente un tipo ed espone i metodi essenziali per ispezionarlo.
AppDomain, Moduli e Assembly
La reflection incentra il suo funzionamento su una classe principale, Assembly, che rappresenta ovviamente un assembly. Da essa derivano
Module e Type, e da quest'ultimo MemberInfo, il tipo base di tutti gli altri derivati FieldInfo, PropertyInfo, EventInfo, MethodInfo (e
ParameterInfo) e ConstructorInfo.Ci sono svariati modi per caricare un assembly:
- Assembly.GetExcecutingAssembly()
Restituisce un riferimento all'assembly che è in esecuzione e dal quale questa chiamata a funzione viene lanciata. - Assembly.GetAssembly(T As System.Type) oppure System.Type.Assembly()
Restituiscono un riferimento all'assembly in cui viene definito il tipo specificato. - Assembly.Load([Nome])
Carica un assembly a partire dal nome completo o parziale. Ad esempio, si può caricare System.Xml dinamicamente con Assembly.Load("System.Xml"). Questa è una funzione, quindi restituisce un riferimento all'assembly caricato. [Nome] può anche essere il nome completo dell'assembly, che comprende nome, versione, cultura e token della chiave pubblica. La chiave pubblica è un lunghissimo codice formato da cifre esadecimali che identificano univocamente il file; il suo token ne è una versione "abbreviata", utile per non scrivere la chiave intera.
Se un assembly viene caricato con Load, esso diviene parte del contesto di esecuzione (vedi oltre), e inoltre il Framework è capace di trovare e caricare le sue dipendenze da altri file (in gergo, "risolvere"). - Assembly.LoadFrom([File])
Carica un assembly a partire dal suo percorso su disco, che può essere relativo o assoluto, e ne restituisce un riferimento. Il file caricato in questo modo diventa parte del contesto di esecuzione di LoadFrom. Inoltre il Framework è in grado di risolverne le dipendenze solo nel caso in cui queste siano presenti nella cartella principale dell'applicazione. - Assembly.LoadFile([File])
Agisce in modo analogo a LoadFrom, ma l'assembly viene caricato in un contesto di esecuzione differente, e il Framework non è in grado di risolverne le dipendenze, a meno che queste non siano state già caricate con i metodi summentovati. - Assembly.ReflectionOnlyLoad([Nome])
Restituisce un riferimento all'assembly con dato [Nome]. Questo non viene caricato in memoria, poichè il metodo serve solamente a ispezionarne gli elementi. - Assembly.ReflectionOnlyLoadFrom([File])
Restituisce un riferimento all'assembly specificato nel percorso [File]. Questo non viene caricato in memoria, poichè il metodo serve solamente a ispezionarne gli elementi.
Ogni sistema operativo utilizza un meccanismo adatto per separare virtualmente ogni singolo programma, ond'evitare che parte del codice di uno possa influenzarne un altro, causando così un crash o ancora peggio, gravi danni al sistema. Su Windows, tale meccanismo viene implementato per mezzo dei processi, ognuno dei quali racchiude al suo interno uno e un solo programma, con le relative risorse e variabili. Infatti queste ultime vengono allocate in memoria usando indirizzi specifici per un solo processo e che quindi non possono venire lette da alcun altro processo. L'ambiente .Net fa in modo che ogni applicazione venga eseguita in un dominio applicativo, o AppDomain (o contesto di esecuzione) che risiede all'interno di un processo: per questo motivo è possibile creare più AppDomain che sono parte di uno stesso processo, ma che godono dello stesso isolamento e quindi della stessa sicurezza, poichè nessun assembly appartenente a un AppDomain può in alcun modo modificare un altro assembly che non sia nello stesso AppDomain. Il vantaggio di questa suddivisione è che AppDomain nello stesso processo possono comunicare in modo molto più efficiente rispetto ad AppDomain in processi diversi. Ad esempio, avrete notato che se si utilizzano due finestre distinte di Mozilla FireFox, c'è un unico processo associato, come nelle immagini seguenti:
Due AppDomain
Un unico processo
È possibile usufruire di alcuni eventi di AppDomain inerenti al caricamento degli assembly, ma lo vedremo in seguito.
Una volta ottenuto un riferimento valido ad un assembly, è possibile analizzarlo richiamando i suoi membri, tra i quali i più significativi che analizzeremo ora sono:
- Fullname : restituisce il nome completo dell'assembly, specificando nome, cultura, versione e token della chiave pubblica
- CodeBase : nel caso l'assembly sia scaricato da internet, ne restituisce la locazione in formato opportuno
- Location : restituisce il percorso su disco dell'assembly
- GlobalAssemblyChace : restituisce True nel caso l'assembly sia stato caricato dalla GAC
Global Assembly Cache (GAC)La cartella fisica in cui vengono depositati tutti gli assembly pubblici. Per assembly pubblico infatti, s'intende ogni assembly accessibile da ogni applicazione su una determinata macchina. La GAC di Windows è di solito posizionata in C:\WINDOWS\assembly e contiene tutte le classi base del .Net Framework. Ecco perchè basta specificare il nome dell'assembly pubblico per caricarlo. - ReflectionOnly : restituisce True se l'assembly è stato caricato per soli scopi di analisi
- GetName() : restituisce un oggetto AssemblyName associato all'assembly corrente
- GetTypes() : restituisce un array di Type che definiscono ogni tipo dichiarato all'interno dell'assembly
Con questo meccanismo si potrebbe ad esempio scrivere una procedura che enumera tutti gli assembly caricati, sfruttando la funzione AppDomain.CurrentDomain.GetAssemblies().Module Module1Sub Main() 'Carica l'assembly mscorlib per soli scopi di analisi. Questo è 'l'assembly principale da cui deriva un pò tutto. Caricare altri 'assembly potrebbe causare il problema della non risoluzione 'delle dipendenze Dim AsmAs Assembly = Assembly.ReflectionOnlyLoad("mscorlib")Dim NameAs AssemblyName = Asm.GetName Console.WriteLine("Nome: " & Name.Name) Console.WriteLine("Versione: " & Name.Version.ToString) Console.WriteLine("Cultura: " & Name.CultureInfo.ToString) Console.Write("Public Key token: ") 'La funzione GetPublicKeyToken restituisce un array di bytes. 'Dato che per convenzione questo viene scritto come una sequenza 'di otto codici esadecimali bisogna prima convertire il byte in 'esadecimale con la funzione Hex For Each BAs Byte In Name.GetPublicKeyToken Console.Write(Hex(B))Next Console.WriteLine() 'Name.ProcessorArchitecture restituisce una sigla indicante il 'processore per cui l'assembly è stato scritto. Può assumere 'diversi valori tra MSIL, X86, IA64, Amd64 e None Console.WriteLine("Processore: " & _ Name.ProcessorArchitecture.ToString) Console.ReadKey()End Sub End Module
Facendo correre il programma di prima con solo questa procedura, si otterrà un output simile:Sub EnumerateAssemblies()Dim AsmAs AssemblyDim NameAs AssemblyName 'AppDomain � una variabile globale, oggetto singleton, da cui si 'possono trarre informazioni sull'AppDomain corrente o crearne degli 'altri. Questa funzione restituisce tutti gli assembly caricati in 'questo AppDomain For Each AsmIn AppDomain.CurrentDomain.GetAssemblies Name = Asm.GetName Console.WriteLine("Nome: " & Name.Name) Console.WriteLine("Versione: " & Name.Version.ToString) Console.Write("Public Key Token: ")For Each BAs Byte In Name.GetPublicKeyToken Console.Write(Hex(B))Next Console.WriteLine() Console.WriteLine()Next End Sub
Nome: mscorlib Versione: 2.0.0.0Public Key Token: B77A5C561934E089 Nome: Microsoft.VisualStudio.HostingProcess.Utilities Versione: 8.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: System.Windows.Forms Versione: 2.0.0.0Public Key Token: B77A5C561934E089 Nome: System Versione: 2.0.0.0Public Key Token: B77A5C561934E089 Nome: System.Drawing Versione: 2.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: Microsoft.VisualStudio.HostingProcess.Utilities.Sync Versione: 8.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: vshost Versione: 8.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: Microsoft.DirectX Versione: 1.0.2902.0Public Key Token: 31BF3856AD364E35 Nome: Microsoft.DirectX.AudioVideoPlayback Versione: 1.0.2902.0Public Key Token: 31BF3856AD364E35 Nome: System.Data Versione: 2.0.0.0Public Key Token: B77A5C561934E089 Nome: System.Deployment Versione: 2.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: System.Xml Versione: 2.0.0.0Public Key Token: B77A5C561934E089 Nome: Microsoft.VisualBasic Versione: 8.0.0.0Public Key Token: B03F5F7F11D5A3A Nome: App Prove Console Versione: 1.0.0.0Public Key Token: Nome: mscorlib.resources Versione: 2.0.0.0Public Key Token: B77A5C561934E089
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



