Casino online











Mercato forex






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.

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.
Gli ultimi due metodi sono stati creati al solo scopo di ispezionare altri assembly, pertanto non si possono istanziare tipi al loro interno, oppure eseguirne altri. Inoltre, tutti gli assembly caricati in questo modo diventano parte di un contesto di ispezione, unico per ogni AppDomain. Fino ad ora, tuttavia, non si è ancora spiegato cosa siano gli AppDomain e i contesti: veniamo quindi al punto.
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
La classe AssemblyName, come si intuisce dal nome, fornisce proprietà per modificare in modo più semplice il lunghissimo nome completo degli assembly. Le sue proprietà importanti sono autoesplicative.
Module Module1
    Sub 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 Asm As Assembly = Assembly.ReflectionOnlyLoad("mscorlib")
        Dim Name As 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 B As 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 
Con questo meccanismo si potrebbe ad esempio scrivere una procedura che enumera tutti gli assembly caricati, sfruttando la funzione AppDomain.CurrentDomain.GetAssemblies().
Sub EnumerateAssemblies()
    Dim Asm As Assembly
    Dim Name As 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 Asm In AppDomain.CurrentDomain.GetAssemblies
        Name = Asm.GetName
        Console.WriteLine("Nome: " & Name.Name)
        Console.WriteLine("Versione: " & Name.Version.ToString)
        Console.Write("Public Key Token: ")
        For Each B As Byte In Name.GetPublicKeyToken
            Console.Write(Hex(B))
        Next
        Console.WriteLine()
        Console.WriteLine()
    Next
End Sub 
Facendo correre il programma di prima con solo questa procedura, si otterrà un output simile:
Nome: mscorlib
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089

Nome: Microsoft.VisualStudio.HostingProcess.Utilities
Versione: 8.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: System.Windows.Forms
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089

Nome: System
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089

Nome: System.Drawing
Versione: 2.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: Microsoft.VisualStudio.HostingProcess.Utilities.Sync
Versione: 8.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: vshost
Versione: 8.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: Microsoft.DirectX
Versione: 1.0.2902.0
Public Key Token: 31BF3856AD364E35

Nome: Microsoft.DirectX.AudioVideoPlayback
Versione: 1.0.2902.0
Public Key Token: 31BF3856AD364E35

Nome: System.Data
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089

Nome: System.Deployment
Versione: 2.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: System.Xml
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089

Nome: Microsoft.VisualBasic
Versione: 8.0.0.0
Public Key Token: B03F5F7F11D5A3A

Nome: App Prove Console
Versione: 1.0.0.0
Public Key Token: 

Nome: mscorlib.resources
Versione: 2.0.0.0
Public Key Token: B77A5C561934E089 




 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.