A45. Reflection, Parte II
L'oggetto basilare della reflection è System.Type: esso rappresenta un tipo e offre funzionalità per enumerare tutti i membri di un
tipo o anche per impostarne dinamicamente campi o proprietà. Bisogna notare che un oggetto Type è univoco per ogni AppDomain: ad esempio
non si possono ottenere due oggetti Type diversi che contengono informazioni sullo stesso tipo String, poichè essi punteranno sempre alla
stessa istanza, non importa in quanti modi diversi possano essere ottenuti. Una dimostrazione?
Nonostante la funzione GetMembers sia molto potente di per sè, ciò che la rende ancor più versatile è il suo overload, che accetta un enumeratore codificato a bit il quale permette di restringere la ricerca ai soli membri che soddisfano determinate caratteristiche. I valori che questo enumeratore, BindingFlags, può assumere sono:
MemberInfo
MethodInfo
FieldInfo
PropertyInfo
EventInfo (per ulteriori informazioni sugli eventi, vedere capitoli 49, 69 e 70)
Esistono svariati modi diversi per ottenere un oggetto Type. È importante evidenziare che Type non ha alcun costruttore poichè non si può creare un tipo, ma si può solo ottenerne un riferimento.Module Module1Sub Main() 'Questi due oggetti Type sono ottenuti in due modi diversi 'ma sempre in riferimento al tipo String Dim Tipo1As Type = GetType(String )Dim Tipo2As Type = Type.GetType("System.String") Console.WriteLine(Tipo1Is Tipo2) '> True 'Come volevasi dimostrare, esiste una e una sola istanza Type 'per ogni tipo in un AppDomain Console.ReadKey()End Sub End Module
Una volta ottenuto un riferimento all'oggetto Assembly voluto, è possibile enumerarne i membri usando la funzione GetTypes() e le proprietà e i metodi degli oggetti Type, tra i quali, quelli che permettono di fare questo sono:Module Module2Sub Main() 'Ecco i modi possibili per ottenere un oggetto Type 'Modo 1: utilizzare l'operatore GetType Dim Tipo1As Type = GetType(Double ) Console.WriteLine(Tipo1.FullName) '> System.Double 'Modo 2: utilizzare la funzione statica GetType 'Da notare che questa funzione ha due overload: il primo accetta 'un ulteriore parametro che specifica se generare un'eccezione nel 'caso in cui non sia possibile ottenere un riferimento al tipo; 'il secondo accetta tutti quelli normali più uno che determina se 'il confronto del nome deve avvenire in case-insensitive (vedi 'capitolo 40). 'Se si usa questa versione di GetType e il tipo specificato non 'esiste sarà semplicemente restituito Nothing Dim Tipo2As Type = Type.GetType("System.String") Console.WriteLine(Tipo2.FullName) '> System.String 'Modo 3: utilizzare la funzione di istanza GetType Dim AAs New ArrayListDim Tipo3As Type = A.GetType Console.WriteLine(Tipo3.FullName) '> System.Collections.ArrayList 'Modo 4: utilizzare la funzione di istanza Assembly.GetTypes Dim AsmAs Assembly = AppDomain.CurrentDomain.GetAssemblies()(0)For Each Tipo4As TypeIn Asm.GetTypes '... Next Console.ReadKey()End Sub End Module
- Assembly : restituisce l'assembly a cui il tipo appartiene
- AssemblyQualifiedName : restituisce il nome dell'assembly a cui il tipo appartiene
- BaseType : se il tipo corrente eredita da una classe base, questa proprietà restituisce un oggetto Type in riferimento a tale classe
- DeclaringMethod : se il tipo corrente è parametro di un metodo, questa proprietà restituisce un oggetto MethodBase che rappresenta tale metodo
- DeclaringType : se il tipo corrente è membro di una classe, questa proprietà restituisce un oggetto Type che rappresenta tale classe
- FullName : il nome completo del tipo corrente; questo è restituito nella forma [Namespace].[Classe]+[Tipo]
- IsAbstract : determina se il tipo è una classe astratta
- IsArray : determina se è un array
- IsByRef : determina, qualora il tipo rappresentasse un parametro, se esso sia passato per indirizzo
- IsClass : determina se è una classe
- IsEnum : determina se è un enumeratore
- IsInterface : determina se è un'interfaccia
- IsNested : determina se il tipo è nidificato: questo significa che rappresenta un membro di classe o di struttura; di conseguenza tutte le proprietà il cui nome inizia per "IsNested" servono a determinare l'ambito di visibilità del membro, e quindi il suo specificatore di accesso
- IsNestedAssembly : determina se il membro è Friend
- IsNestedFamily : determina se il membro è Protected
- IsNestedFamORAssem : determina se il membro è Protected Friend
- IsNestedPrivate : determina se il membro è Private
- IsNestedPublic : determina se il membro è Public
- IsNotPublic : determina se il tipo non è Public (solo per tipi non nidificati)
- IsPointer : determina se è un puntatore
- IsPrimitive : determina se è uno dei tipi primitivi
- IsPublic : determina se il tipo è Public (solo per tipi non nidificati)
- IsSealed : determina se è una classe sigillata
- IsValueType : determina se è un tipo value
- Name : il nome del tipo corrente
- Namespace : il namespace in cui è contenuto il tipo corrente
Module Module2Sub EnumerateTypes(ByVal AsmAs Assembly)Dim ClsAs String For Each TAs TypeIn Asm.GetTypesIf T.IsClassThen Cls = "Class"ElseIf T.IsInterfaceThen Cls = "Interface"ElseIf T.IsEnumThen Cls = "Enumerator"ElseIf T.IsValueTypeThen Cls = "Structure/Value"End If Console.WriteLine("{0} ({1})", T.Name, Cls)Next End Sub Sub Main()Dim AsmAs Assembly = Assembly.Load("mscorlib") 'Questa chiamata restituirà svariate decine se non centinaia 'di elementi, tutti classificati secondo quanto scritto prima EnumerateTypes(Asm) Console.ReadKey()End Sub End Module
Enumerare i membri
Come avrete sicuramente notato scorrendo la gigantesca quantità di metodi e proprietà di System.Type, ci sono funzioni specifiche per ottenere
ogni tipo di membro. In particolare, quelle con il nome al plurale (come GetEvents, GetMethods, eccetera) restituiscono un array di oggetti
MemberInfo. In realtà MemberInfo è una classe astratta da cui derivano tipi più specifici, come EventInfo o MethodInfo, che si ottengono
richiamando le rispettive funzioni. Per enumerare ogni membro di un tipo, è possibile usare GetMembers. È da notare che se ci sono metodi
o proprietà in overload, ci sarà un numero corrispondente di membri, anche se omonimi, nell'array restituito. Ad esempio, si provi il
seguente codice:
A schermo appare una serie di nomi: rappresentano tutti i membri di ArrayList, molti metodi, alcune proprietà e alcuni costruttori. È interessante notare che i blocchi Get e Set di una proprietà sono trattati come metodi: ad esempio, come output apparirà get_IsFixedReadOnly, che rappresenta il blocco Get della proprietà IsFixedReadOnly.Module Module3Sub Main()Dim TAs Type = GetType(ArrayList)For Each MAs MemberInfoIn T.GetMembers 'La proprietà MemberType restituisce un enumeratore che 'specifica di che tipo sia il membro, se una struttura, un 'metodo, un costruttore, eccetera... Console.WriteLine(M.MemberType.ToString & " " & M.Name)Next Console.ReadKey()End Sub End Module
Nonostante la funzione GetMembers sia molto potente di per sè, ciò che la rende ancor più versatile è il suo overload, che accetta un enumeratore codificato a bit il quale permette di restringere la ricerca ai soli membri che soddisfano determinate caratteristiche. I valori che questo enumeratore, BindingFlags, può assumere sono:
- Public : solo membri Public
- NonPublic : solo membri non Public; è necessario usare almeno uno fra questo e il precedente per ottenere un risultato che non sia vuoto (infatti non esistono membri che non sono nè Public nè Non Public...)
- Instance : solo membri d'istanza
- Static : solo membri shared
- DeclaredOnly : solo membri dichiarati fisicamente in questi tipo, ossia non ereditati dalla classe base (se c'e')
Se si sta cercando un membro in particolare, invece, sarebbe opportuno usare solo le funzioni GetX (dove X rappresenta uno qualsiasi tra Events, Methods, Fields, Constructors, Properties, eccetera...). Ecco un piccolo elenco delle proprietà e metodi importanti di MemberInfo e dei suoi derivati:Module Module4Sub Main()Dim TAs Type = GetType(String ) 'Solo metodi pubblici statici For Each MAs MemberInfoIn T.GetMembers(BindingFlags.PublicOr _ BindingFlags.Static) Console.WriteLine(M.MemberType.ToString & " " & M.Name)Next Console.ReadKey()End Sub End Module
MemberInfo
- DeclaringType : la classe che dichiara questo membro
- MemberType : tipo di membro
- Name : il nome del membro
- ReflectedType : il tipo usato per ottenere un riferimento a questo membro tramite reflection
MethodInfo
- GetBaseDefinition() : se il metodo è modificato tramite polimorfismo, restituisce la versione della classe base
- GetCurrentMethod() : restituisce un MethodInfo in riferimento al metodo in cui questa funzione viene chiamata
- GetMethodBody() : restituisce un MethodBody (che vedremo in seguito) contenente informazioni sulle variabili locali, le eccezioni e il codice IL
- GetParameters() : restituisce un elenco di ParameterInfo rappresentanti i parametri
- IsAbstract : determina se il metodo è MustOverride
- IsConstructor : determina se è un costruttore
- IsFinal : determina se è NotOverridable (keyword non analizzata ma di cui si può comprende il significato)
- IsStatic : determina se è Shared
- IsVirtual : determina se è Overridable
- ReturnParameter : qualora il metodo fosse una funzione, restituisce informazioni sul valore restituito
- ReturnType : in una funzione, restituisce l'oggetto Type associato al tipo restituito
FieldInfo
- GetRawCostantValue() : se il campo è una costante, ne restituisce il valore
- GetValue(O) : in fase di esecuzione, restituisce il valore della variabile nell'oggetto O
- IsLiteral : determina se è una costante
- IsInitOnly : determina se è una variabile readonly
- SetValue(O, V) : in fase di esecuzione, imposta il valore della variabile nell'oggetto O
PropertyInfo
- CanRead : determina se si può leggere la proprieta'
- CanWrite : determina se si può impostare la proprieta'
- GetGetMethod() : restituisce un MethodInfo corrispondente al blocco Get
- GetSetMethod() : restituisce un MethodInfo corrispondente al blocco Set
- GetPropertyType() : restituisce un oggetto Type in riferimento al tipo della proprieta'
EventInfo (per ulteriori informazioni sugli eventi, vedere capitoli 49, 69 e 70)
- AddEventHandler(O, H) : aggiunge un handler d'evento all'oggetto O per l'evento corrispondente a questo EventInfo
- GetAddMethod() : restituisce un riferimento al metodo usato per aggiungere gli handler d'evento
- GetRaiseMethod() : restituisce un riferimento al metodo che viene richiamato quando si scatena l'evento
- GetRemoveMethod() : restituisce un riferimento al metodo usato per rimuovere gli handler d'evento
- IsMulticast : indica se l'evento è gestito tramite un delegate multicast
- RemoveEventHandler(O, H) : rimuove un handler d'evento dall'oggetto O per l'evento corrispondente a questo EventInfo
Facendo correre il programma e scegliendo ad esempio Test (il mio modulo ha molti metodi tra quelli usati nelle lezioni precedenti), si otterrà un output simile:Module Module1Sub AnalyzeMethod(ByVal MIAs MethodInfo) 'Il nome Dim NameAs String 'Il nome completo, con scpecificatori di accesso, modificatori, 'signature e tipo restituito. Per ulteriori informazioni sul tipo 'StringBuilder, vedere capitolo 74 Dim CompleteNameAs New System.Text.StringBuilder 'Lo specificatore di accesso Dim ScopeAs String 'Gli eventuali modificatori Dim ModifierAs String 'La categoria: se è Sub o Function Dim CategoryAs String 'La signature del metodo, che andremo a costruire Dim SignatureAs New System.Text.StringBuilder 'Di solito, tutti i metodi hanno un tipo restituito, poichè 'in analogia con la sintassi del C, una procedura è una funzione 'che restituisce Void, ossia niente. Per questo bisogna 'controllare anche il nome del tipo di ReturnParameter If MI.ReturnParameterIsNot Nothing AndAlso _ MI.ReturnType.FullName <> "System.Void"Then Category = "Function"Else Category = "Sub"End If If MI.IsConstructorThen Name = "New"Else Name = MI.NameEnd If If MI.IsAssemblyThen Scope = "Friend"ElseIf MI.IsFamilyThen Scope = "Protected"ElseIf MI.IsFamilyOrAssemblyThen Scope = "Protected Friend"ElseIf MI.IsPrivateThen Scope = "Private"Else Scope = "Public"End If If MI.IsFinalThen 'Vorrei far notare una sottigliezza. Se il metodo è Final, 'ossia NotOverridable, significa che non può essere 'modificato nelle classi derivate. Ma tutti i membri non 'dichiarati esplicitamente Overridable non sono modificabili 'nelle classi derivate. In definitiva, definire un metodo 'senza modificatori polimorfici (come quelli che seguono 'qua in basso), equivale a definirlo NotOverridable. 'Perciò, non aggiungerò nessun modificatore 'in questo caso ElseIf MI.IsAbstractThen Modifier = "MustOverride"ElseIf MI.IsVirtualThen Modifier = "Overridable"ElseIf MI.GetBaseDefinitionIsNot Nothing AndAlso _ MIIsNot MI.GetBaseDefinitionThen Modifier = "Overrides"End If If MI.IsStaticThen If Modifier <> ""Then Modifier = "Shared " & ModifierElse Modifier = "Shared"End If End If 'Inizia la signature con una parentesi 'Append aggiunge una stringa a Signature Signature.Append("(")For Each PAs ParameterInfoIn MI.GetParameters 'Se P è un parametro successivo al primo, lo separa dal 'precedente con una virgola If P.Position > 0Then Signature.Append(", ")End If 'Se P è passato per valore, ci vuole ByVal, altrimenti ByRef If P.ParameterType.IsByRefThen Signature.Append("ByRef ")Else Signature.Append("ByVal ")End If 'Se P è opzionale, ci vuole la keyword Optional If P.IsOptionalThen Signature.Append("Optional ")End If 'Dato che la sintassi del nome è in stile C, al posto delle 'parentesi tonde in un array ci sono delle quadre: rimediamo Signature.Append(P.Name)If P.ParameterType.IsArrayThen Signature.Append("()")End If Signature.Append("As " & P.ParameterType.Name) 'Si ricordi che i parametri optional hanno un valore 'di default If P.IsOptionalThen Signature.Append(" = " & P.DefaultValue.ToString)End If Next Signature.Append(")")If MI.ReturnParameterIsNot Nothing AndAlso _ MI.ReturnType.FullName <> "System.Void"Then Signature.Append("As " & MI.ReturnType.Name)End If 'Ed ecco il nome completo CompleteName.AppendFormat("{0} {1} {2} {3}{4}", Scope, Modifier, _ Category, Name, Signature.ToString) Console.WriteLine(CompleteName.ToString) Console.WriteLine() 'Ora ci occupiamo del corpo Dim MBAs MethodBody = MI.GetMethodBodyIf MBIs Nothing Then Exit Sub End If 'Massima memoria occupata sullo stack Console.WriteLine("Massima memoria stack : {0} bytes", _ MB.MaxStackSize) Console.WriteLine() 'Variabili locali (LocalVariableInfo è una variante di FieldInfo) Console.WriteLine("Variabili locali:")For Each LAs LocalVariableInfoIn MB.LocalVariables 'Dato che non si può ottenere il nome, ci si deve 'accontentare di un indice Console.WriteLine(" Var({0})As {1}", L.LocalIndex, _ L.LocalType.Name)Next Console.WriteLine() 'Gestione delle eccezioni Console.WriteLine("Gestori di eccezioni:")For Each ExAs ExceptionHandlingClauseIn MB.ExceptionHandlingClauses 'Tipo di clausola: distingue tra filtro (When), 'clausola (Catch) o un blocco Finally Console.WriteLine(" Tipo : {0}", Ex.Flags.ToString) 'Se si tratta di un blocco Catch, ne specifica la natura If Ex.Flags = ExceptionHandlingClauseOptions.ClauseThen Console.WriteLine(" Catch Ex As " & Ex.CatchType.Name)End If 'Offset, ossia posizione in bytes nel Try, del gestore Console.WriteLine(" Offset : {0}", Ex.HandlerOffset) 'Lunghezza, in bytes, del codice eseguibile del gestore Console.WriteLine(" Lunghezza : {0}", Ex.HandlerLength) Console.WriteLine()Next End Sub Sub Test(ByVal NumAs Int32,ByVal SAs String )Dim TAs Date Dim VAs String Try Console.WriteLine("Prova 1, 2, 3")Catch ExAs ArithmeticException Console.WriteLine("Errore 1")Catch ExAs ArgumentException Console.WriteLine("Errore 2")Finally Console.WriteLine("Ciao")End Try End Sub Sub Main()Dim TAs Type = GetType(Module1)Dim Methods()As MethodInfo = T.GetMethodsDim IndexAs Int16 Console.WriteLine("Inserire un numero tra i seguenti per " & _ "analizzare il metodo corrispondente:") Console.WriteLine()For IAs Int16 = 0To Methods.Length - 1 Console.WriteLine("{0} - {1}", I, Methods(I).Name)Next Console.WriteLine() Index = Console.ReadLineIf Index >= 0And Index < Methods.LengthThen AnalyzeMethod(Methods(Index))End If Console.ReadKey()End Sub End Module
Public Shared Sub Test(ByVal Num As Int32, ByVal S As String)
Massima memoria stack : 2 bytes
Variabili locali:
Var(0) As DateTime
Var(1) As String
Var(2) As ArithmeticException
Var(3) As ArgumentException
Gestori di eccezioni:
Tipo : Clause
Catch Ex As ArithmeticException
Offset : 15
Lunghezza : 26
Tipo : Clause
Catch Ex As ArgumentException
Offset : 41
Lunghezza : 26
Tipo : Finally
Offset : 67
Lunghezza : 13
Prima di concludere, vorrei porre l'attenzione su un argomento già visto, ma che trova qui la sua conferma: l'Imports statico. Con questo
programma si è dimostrato che tutti i membri di un modulo sono shared!
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



