Casino online











Mercato forex






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?
Module Module1
    Sub Main()
        'Questi due oggetti Type sono ottenuti in due modi diversi
        'ma sempre in riferimento al tipo String
        Dim Tipo1 As Type = GetType(String)
        Dim Tipo2 As Type = Type.GetType("System.String")

        Console.WriteLine(Tipo1 Is Tipo2)
        '> True
        'Come volevasi dimostrare, esiste una e una sola istanza Type
        'per ogni tipo in un AppDomain

        Console.ReadKey()
    End Sub
End Module 
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 Module2
    Sub Main()
        'Ecco i modi possibili per ottenere un oggetto Type

        'Modo 1: utilizzare l'operatore GetType
        Dim Tipo1 As 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 Tipo2 As Type = Type.GetType("System.String")
        Console.WriteLine(Tipo2.FullName)
        '> System.String

        'Modo 3: utilizzare la funzione di istanza GetType
        Dim A As New ArrayList
        Dim Tipo3 As Type = A.GetType
        Console.WriteLine(Tipo3.FullName)
        '> System.Collections.ArrayList

        'Modo 4: utilizzare la funzione di istanza Assembly.GetTypes
        Dim Asm As Assembly = AppDomain.CurrentDomain.GetAssemblies()(0)
        For Each Tipo4 As Type In Asm.GetTypes
            '...
        Next

        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:
  • 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
Ad esempio, il seguente codice enumera tutti i tipi definiti in un assembly:
Module Module2
    Sub EnumerateTypes(ByVal Asm As Assembly)
        Dim Cls As String

        For Each T As Type In Asm.GetTypes
            If T.IsClass Then
                Cls = "Class"
            ElseIf T.IsInterface Then
                Cls = "Interface"
            ElseIf T.IsEnum Then
                Cls = "Enumerator"
            ElseIf T.IsValueType Then
                Cls = "Structure/Value"
            End If
            Console.WriteLine("{0} ({1})", T.Name, Cls)
        Next
    End Sub

    Sub Main()
        Dim Asm As 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:
Module Module3
    Sub Main()
        Dim T As Type = GetType(ArrayList)

        For Each M As MemberInfo In 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 
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.
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')
Module Module4
    Sub Main()
        Dim T As Type = GetType(String)

        'Solo metodi pubblici statici
        For Each M As MemberInfo In T.GetMembers(BindingFlags.Public Or _
            BindingFlags.Static)
            Console.WriteLine(M.MemberType.ToString & " " & M.Name)
        Next

        Console.ReadKey()
    End Sub
End Module 
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:

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
Non scriverò un esempio per ognuno di questi, ma solo per i metodi, che comportano un percorso più complesso. Si ricordi, comunque, che è possibile sapere se un MethodInfo è di uno di questi tipi derivati con TypeOf.
Module Module1
    Sub AnalyzeMethod(ByVal MI As MethodInfo)
        'Il nome
        Dim Name As String
        'Il nome completo, con scpecificatori di accesso, modificatori,
        'signature e tipo restituito. Per ulteriori informazioni sul tipo
        'StringBuilder, vedere capitolo 74
        Dim CompleteName As New System.Text.StringBuilder
        'Lo specificatore di accesso
        Dim Scope As String
        'Gli eventuali modificatori
        Dim Modifier As String
        'La categoria: se è Sub o Function
        Dim Category As String
        'La signature del metodo, che andremo a costruire
        Dim Signature As 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.ReturnParameter IsNot Nothing AndAlso _
            MI.ReturnType.FullName <> "System.Void" Then
            Category = "Function"
        Else
            Category = "Sub"
        End If

        If MI.IsConstructor Then
            Name = "New"
        Else
            Name = MI.Name
        End If

        If MI.IsAssembly Then
            Scope = "Friend"
        ElseIf MI.IsFamily Then
            Scope = "Protected"
        ElseIf MI.IsFamilyOrAssembly Then
            Scope = "Protected Friend"
        ElseIf MI.IsPrivate Then
            Scope = "Private"
        Else
            Scope = "Public"
        End If

        If MI.IsFinal Then
            '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.IsAbstract Then
            Modifier = "MustOverride"
        ElseIf MI.IsVirtual Then
            Modifier = "Overridable"
        ElseIf MI.GetBaseDefinition IsNot Nothing AndAlso _
            MI IsNot MI.GetBaseDefinition Then
            Modifier = "Overrides"
        End If

        If MI.IsStatic Then
            If Modifier <> "" Then
                Modifier = "Shared " & Modifier
            Else
                Modifier = "Shared"
            End If
        End If

        'Inizia la signature con una parentesi
        'Append aggiunge una stringa a Signature
        Signature.Append("(")
        For Each P As ParameterInfo In MI.GetParameters
            'Se P è un parametro successivo al primo, lo separa dal
            'precedente con una virgola
            If P.Position > 0 Then
                Signature.Append(", ")
            End If

            'Se P è passato per valore, ci vuole ByVal, altrimenti ByRef
            If P.ParameterType.IsByRef Then
                Signature.Append("ByRef ")
            Else
                Signature.Append("ByVal ")
            End If

            'Se P è opzionale, ci vuole la keyword Optional
            If P.IsOptional Then
                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.IsArray Then
                Signature.Append("()")
            End If
            Signature.Append(" As " & P.ParameterType.Name)

            'Si ricordi che i parametri optional hanno un valore 
            'di default
            If P.IsOptional Then
                Signature.Append(" = " & P.DefaultValue.ToString)
            End If
        Next
        Signature.Append(")")

        If MI.ReturnParameter IsNot 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 MB As MethodBody = MI.GetMethodBody

        If MB Is 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 L As LocalVariableInfo In 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 Ex As ExceptionHandlingClause In 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.Clause Then
                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 Num As Int32, ByVal S As String)
        Dim T As Date
        Dim V As String

        Try
            Console.WriteLine("Prova 1, 2, 3")
        Catch Ex As ArithmeticException
            Console.WriteLine("Errore 1")
        Catch Ex As ArgumentException
            Console.WriteLine("Errore 2")
        Finally
            Console.WriteLine("Ciao")
        End Try
    End Sub

    Sub Main()
        Dim T As Type = GetType(Module1)
        Dim Methods() As MethodInfo = T.GetMethods
        Dim Index As Int16

        Console.WriteLine("Inserire un numero tra i seguenti per " & _
            "analizzare il metodo corrispondente:")
        Console.WriteLine()
        For I As Int16 = 0 To Methods.Length - 1
            Console.WriteLine("{0} - {1}", I, Methods(I).Name)
        Next
        Console.WriteLine()
        Index = Console.ReadLine

        If Index >= 0 And Index < Methods.Length Then
            AnalyzeMethod(Methods(Index))
        End If

        Console.ReadKey()
    End Sub
End Module 
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:
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.