Casino online











Mercato forex






A46. Reflection, Parte III


Reflection dei Generics
I Generics sono un pò diversi in tutto, e anche la loro reflection non fa eccezione. Se si sta enumerando un insieme di tipi appartenenti ad un assembly, quelli definiti generic presentano una struttura atipica. Il loro nome potrebbe, ad esempio, apparire così:
System.Collection.Generic.List`1
System.Collection.Generic.Dictionary`2  
Infatti, nei generics, il nome del tipo è seguito da un carattere "apostrofo rovesciato" (`) e dal numero di parametri generic dichiarati. È stata adottata questa nomenclatura poichè l'unica cosa che fa davvero differenza nella signature di tipi generic è il numero degli argomenti, poichè il tipo può variare a seconda di come viene dichiarato dal programmatore. Perciò, al fine di ottenere il vero nome del tipo, bisogna eseguire qualche operazione di taglia e cuci con le stringhe: inoltre, per ottenere il nome dei parametri, si deve usare la funzione GetGenericArguments(). Un piccolo esempio:
Module Module1
   Sub EnumerateGenerics(ByVal Asm As Assembly)
        For Each T As Type In Asm.GetTypes
            'Controlla se è generic
            If T.IsGenericTypeDefinition Then
                'Nome del tipo
                Dim Name As String
                'Ottiene una stringa in cui elimina tutti i
                'caratteri a partire dall'indice del �
                Name = T.FullName.Remove(T.FullName.IndexOf("`"))
                'E poi gli aggiunge un "(Of ", per far vedere che si
                'sta iniziando una dichiarazione generic
                Name &= "(Of "
                'Quindi aggiunge tutti gli argomenti generic
                For Each GenT As Type In T.GetGenericArguments
                    'Se il parametro non è il primo, lo separa dal
                    'precedente con una virgola
                    If GenT.GenericParameterPosition > 0 Then
                        Name &= ", "
                    End If
                    'Quindi vi aggiunge il nome
                    Name &= GenT.Name
                Next
                'E chiude la parentesi
                Name &= ")"
                Console.WriteLine(Name)
            End If
        Next
    End Sub

    Sub Main()
        'Ottiene un riferimento al primo assembly che è in esecuzione
        Dim Asm As Assembly = AppDomain.CurrentDomain.GetAssemblies()(0)

        EnumerateGenerics(Asm)

        Console.ReadKey()
    End Sub
End Module  
Apparirà una schermata come questa:


Output

Per i metodi si adotta un procedimento simile: le proprietà e i metodi da usare sono gli stessi, fatta eccezione per i ParameterInfo. Nella lezione precedente si è visto come poter enumerare i parametri di un metodo: la proprietà IsGenericParameter di ParameterInfo specifica se quel parametro è di un tipo generic. Bisogna ricordare, però, che i metodi generic possono apparire sia in classi generic sia in classi normali o in moduli. Ad esempio
'Classe generic
Class Gen(Of T1, T2)
    'Metodo generic in classe generic: GetGenericArguments restituisce
    'T1 e T2, poichè il metodo li usa come tipi nella sua signature.
    'Sarebbe come dichiarare implicitamente ASub(Of T1, T2)
    Public Sub ASub(ByVal A As T1, ByVal B As T2)
        '...
    End Sub
    
    'Metodo normale in classe generic: niente di speciale
    Public Sub BSub(ByVal C As Int32)
        '...
    End Sub
End Class

'Metodo generic da solo
Public Sub CSub(Of K1, K2)(ByRef A As K1, ByRef B As K2)
    '...
End Sub  
Ora si è in grado di eseguire correttamente la reflection di tipi o metodi generic, ma come si opera quando ci sono tipi generic collegati? Il meccanismo è semplice, poichè in questi casi, IsGenericType è True e GetGenericArguments restituisce un array di tipi normali. Ad esempio, con questi due campi:
Public L As New List(Of Int32)
Public I As Int32  
si vuole creare una procedura che analizzi L. Dato che L è un campo, si otterranno tutti i FieldInfo del tipo in cui L è contenuto. Successivamente si analizzeranno le proprietà FieldInfo.FieldType.IsGenericType per sapere se il tipo del campo è generic. In questo caso, si passerà ad enumerare tutti i parametri generic come se fossero delle normalissime definizioni di tipi generic: in realtà essi saranno sostituiti dai tipi collegati, come, in questo caso, Int32.
Module Module1
    'Enumera solo i campi generic di un tipo
    Sub EnumerateGenericFieldMembers(ByVal T As Type)
        For Each F As FieldInfo In T.GetFields()
            'Console.WriteLine(F.FieldType)
            If F.FieldType.IsGenericType Then
                Dim GenericName As String
                Dim ThisType As Type = F.FieldType
                Dim I As Int16 = 0

                GenericName = ThisType.FullName.Remove(_
                  ThisType.FullName.IndexOf("`"))
                GenericName &= "(Of "
                For Each GenP As Type In ThisType.GetGenericArguments
                    'Dato che non si stanno analizzando dei parametri 
                    'generic, non si può utilizzare la proprietà 
                    'GenericParameterPosition
                    If I > 0 Then
                        GenericName &= ", "
                    End If
                    GenericName &= GenP.Name
                    I += 1
                Next
                GenericName &= ")"
                Console.WriteLine("Dim {0} As {1}", F.Name, GenericName)
            End If
        Next
    End Sub

    Public L As New List(Of Integer)
    Public I As Int32

    Sub Main()
        EnumerateGenericFieldMembers(GetType(Module1))

        Console.ReadKey()
    End Sub
End Module  


Creare un Object Browser per assembly
Ora che si sono analizzate tutte le aree più importanti della reflection, è possibile scrivere un programma che analizzi un assembly enumerando tutti i tipi e i membri, facendo anche attenzione ai generics. Questo esempio riassume tutte le tecniche viste fin'ora:
Module Module1
    Class AssemblyScanner
        Private _Assembly As Assembly

        'L'assembly da analizzare
        Public Property Assembly() As Assembly
            Get
                Return _Assembly
            End Get
            Set(ByVal Value As Assembly)
                If Value IsNot Nothing Then
                    _Assembly = Value
                End If
            End Set
        End Property

        Sub New(ByVal Asm As Assembly)
            Me.Assembly = Asm
        End Sub

        'Funzione privata che restituisce lo specificatore di accesso di
        'un membro.
        'Questa funzione utilizza un meccanismo di late-binding per 
        'risparmiare spazio, seguendo il seguente ragionamento: non si 
        'può specificare come parametro un oggetto di tipo MemberInfo, 
        'poiché questo non espone le proprità che servono 
        'alla funzione; scrivere un overload di funzioni sprecherebbe 
        'troppo spazio; la conclusione logica più semplice è 
        'quella di usare un parametro di tipo Object, che poichè 
        'la funzione è privata, sarà sempre dotato di 
        'tali proprietà in quanto saremo noi stessi a 
        'passare parametri adeguati
        Private Function GetScope(ByVal MI As Object) As String
            Dim Scope As String
            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"
            ElseIf MI.IsPublic Then
                Scope = "Public"
            End If
            Return Scope
        End Function

        'Determina se il membro ha un nome speciale. Ad esempio, si è 
        visto che la reflection considera Get e Set di una proprietà 
        'come metodi a sè stanti. Perciò se c'è una 
        'proprietà A in una classe, la reflection enumererà 
        'anche i metodi get_A e set_A, che corrispondono ai blocchi Get 
        'e Set. Per evitare questo, e altri problemi simili, >si deve 
        'controllare che IsSpecialName sia false.
        Private Function IsSpecialName(ByVal Member As Object) As Boolean
            Return Member.IsSpecialName
        End Function

        'Funzione privata che restituisce il nome di un tipo. Utilizza 
        'un algoritmo ricorsivo, poichè potrebbero anche esserci 
        'generic nidificati, come ad esempio:
        'Dim A As New List(Of Dictionary(Of String, Of Nullable(Of Integer)))
        'Non si sa mai...
        Private Function GetTypeName(ByVal T As Type) As String
            Dim FullName As New StringBuilder

            If T.IsGenericType And T.Name.IndexOf("`") >= 0 Then
                Dim Position As Int16 = 0
                FullName.Append(T.Name.Remove(T.Name.IndexOf("`")))
                FullName.Append("(Of ")
                For Each GenArg As Type In T.GetGenericArguments
                    If Position > 0 Then
                        FullName.Append(", ")
                    End If
                    'Qui la funzione richiama sè stessa
                    FullName.Append(GetTypeName(GenArg))
                Next
                FullName.Append(")")
            Else
                FullName.Append(T.Name)
            End If

            Return FullName.ToString
        End Function

        'Analizza un tipo e ne restituisce il nome nella forma:
        '[Scope] [Modifier] [Category] [Name]
        'Ad esmpio:
        'Public MustInherit Class Ciao
        Private Function GetTypeInfo(ByVal T As Type) As String
            Dim CompleteName As New StringBuilder

            'Non si può utilizzare la funzione GetScope poichè le 
            'proprietà di Type inerenti all'ambito di visibilità 
            'hanno nome diverso
            If T.IsNested Then
                'Scope di tipi nidificati
                If T.IsNestedAssembly Then
                    CompleteName.Append("Friend ")
                ElseIf T.IsNestedFamily Then
                    CompleteName.Append("Protected ")
                ElseIf T.IsNestedFamORAssem Then
                    CompleteName.Append("Protected Friend ")
                ElseIf T.IsNestedPrivate Then
                    CompleteName.Append("Private ")
                ElseIf T.IsNestedPublic Then
                    CompleteName.Append("Public ")
                End If
            Else
                'Scope di tipi non nidificati
                If T.IsPublic Then
                    CompleteName.Append("Public ")
                ElseIf T.IsNotPublic Then
                    'Dato che per i tipi non nidificati non si può sapere
                    'con precisione lo scope, se non sono pubblici si 
                    'mette un generico NotPublic. Questa non è una 
                    'keyword del Vb.Net
                    CompleteName.Append("NotPublic ")
                End If
            End If

            'Modificatori vari di ereditarietà (valgono solo 
            'per le classi)
            If T.IsClass Then
                If T.IsAbstract Then
                    CompleteName.Append("MustInherit ")
                ElseIf T.IsSealed Then
                    CompleteName.Append("NotInheritable ")
                End If
            End If

            'Categoria del tipo
            If T.IsClass Then
                CompleteName.Append("Class ")
            ElseIf T.IsEnum Then
                CompleteName.Append("Enum ")
            ElseIf T.IsInterface Then
                CompleteName.Append("Interface ")
            ElseIf T.IsValueType Then
                'Dato che Type rappresenta un tipo, solo le struttura sono
                'comprese nei tipi Value, poichè non è possibile definire
                'tipi base come Char e String
                CompleteName.Append("Structure ")
            End If

            CompleteName.Append(T.Name)

            'Se è una dichiarazione di un tipo generic, bisogna 
            'ricordarsi di aggiunge la signature degli argomenti generic.
            'Attenzione! Type.IsGenericType e 
            'Type.IsGenericTypeDefinition sono proprietà diverse! 
            'La prima specifica un tipo generic collegato mentre la 
            'seconda una definizione di tipo generic. Ad esempio:
            '"Class Ciao(Of T1, T2)" oppure "Sub Ciao(Of K1, K2)"
            'Sono definizioni di tipi generic, mentre
            '"Dim A As List(Of Integer)" e "..., ByVal Elem1 As K1"
            'Sono tipi generic collegati
            If T.IsGenericTypeDefinition Then
                Dim Position As Int16 = 0
                CompleteName.Append("(Of ")
                For Each GenArg As Type In T.GetGenericArguments
                    If Position > 0 Then
                        CompleteName.Append(", ")
                    End If
                    CompleteName.Append(GenArg.Name)
                    Position += 1
                Next
                CompleteName.Append(")")
            End If

            Return CompleteName.ToString
        End Function

        'Analizza un metodo e ne restituisce il nome nella forma:
        '[Scope] (Shared) [Modifier] [Category] [Name][Signature]
        'Ad esempio:
        'Protected Overridable Sub Ciao(Of T)(ByVal A As List(Of T))
        Private Function GetMethodInfo(ByVal MI As MethodInfo) As String
            Dim CompleteName As New StringBuilder

            'MethodInfo espone tutte le proprietà necessarie, quindi e'
            'lecito passarlo come parametro a GetScope
            CompleteName.Append(GetScope(MI) & " ")

            'Se è shared oppure no
            If MI.IsStatic Then
                CompleteName.Append("Shared ")
            End If

            'Modificatori
            If MI.IsAbstract Then
                CompleteName.Append("MustOverride ")
            ElseIf MI.IsFinal Then
                'Niente
            ElseIf MI.IsVirtual Then
                CompleteName.Append("Overridable ")
            End If

            'Categoria
            If MI.ReturnType IsNot GetType(Void) Then
                CompleteName.Append("Function ")
            Else
                CompleteName.Append("Sub ")
            End If

            'Se e'un costruttore oppure no
            If MI.IsConstructor Then
                CompleteName.Append("New")
            Else
                CompleteName.Append(MI.Name)
            End If

            'Signature
            Dim Position As Int16 = 0

            'Potrebbe essere un metodo generic
            If MI.IsGenericMethodDefinition Then
                CompleteName.Append("(Of ")
                For Each GenArg As Type In MI.GetGenericArguments
                    If Position > 0 Then
                        CompleteName.Append(", ")
                    End If
                    CompleteName.Append(GenArg.Name)
                    Position += 1
                Next
                CompleteName.Append(")")
                Position = 0
            End If

            CompleteName.Append("(")
            For Each ArgTy As ParameterInfo In MI.GetParameters
                If Position > 0 Then
                    CompleteName.Append(", ")
                End If
                If ArgTy.ParameterType.IsByRef Then
                    CompleteName.Append("ByRef ")
                Else
                    CompleteName.Append("ByVal ")
                End If
                If ArgTy.IsOptional Then
                    CompleteName.Append("Optional ")
                End If
                CompleteName.Append(ArgTy.Name)
                CompleteName.AppendFormat(" As {0}", _
                  GetTypeName(ArgTy.ParameterType))
                If ArgTy.IsOptional Then
                    CompleteName.AppendFormat(" = {0}", _
                      ArgTy.DefaultValue.ToString)
                End If
                Position += 1
            Next
            CompleteName.Append(")")

            If MI.ReturnType IsNot GetType(Void) Then
                CompleteName.AppendFormat(" As {0}", _ 
                    GetTypeName(MI.ReturnType))
            End If

            Return CompleteName.ToString
        End Function

        'Analizza un campo e ne restituisce il nome nella forma:
        '(Const)/[Scope] [Modifier] [Nome] As [Tipo] (= [Costante])
        Private Function GetFieldInfo(ByVal FI As FieldInfo) As String
            Dim CompleteName As New StringBuilder

            CompleteName.Append(GetScope(FI) & " ")

            'Costante
            If FI.IsLiteral Then
                CompleteName.Append("Const ")
            ElseIf FI.IsInitOnly Then
                CompleteName.Append("ReadOnly ")
            End If

            CompleteName.Append(FI.Name & " ")
            CompleteName.AppendFormat("As {0}", GetTypeName(FI.FieldType))

            If FI.IsLiteral Then
                CompleteName.AppendFormat(" = {0}", FI.GetRawConstantValue)
            End If

            Return CompleteName.ToString
        End Function

        'Analizza una proprietà e ne restituisce il nome:
        '[Modifier] Property [Nome][Signature]
        Private Function GetPropertyInfo(ByVal PI As PropertyInfo) As String
            Dim CompleteName As New StringBuilder

            If PI.CanRead Then
                If PI.CanWrite Then
                    'Niente
                Else
                    CompleteName.Append("ReadOnly ")
                End If
            Else
                CompleteName.Append("WriteOnly ")
            End If

            CompleteName.Append("Property ")
            CompleteName.Append(PI.Name)

            'Signature
            Dim Position As Int16 = 0
            CompleteName.Append("(")
            For Each Arg As ParameterInfo In PI.GetIndexParameters
                If Position > 0 Then
                    CompleteName.Append(", ")
                End If
                CompleteName.Append("ByVal ")
                If Arg.IsOptional Then
                    CompleteName.Append("Optional ")
                End If
                CompleteName.Append(Arg.Name)
                CompleteName.AppendFormat(" As {0}", _
                  GetTypeName(Arg.ParameterType))
                If Arg.IsOptional Then
                    CompleteName.AppendFormat(" = {0}", _
                      Arg.DefaultValue.ToString)
                End If
            Next
            CompleteName.AppendFormat(") As {0}", _
                GetTypeName(PI.PropertyType))

            Return CompleteName.ToString
        End Function

        'Analizza un evento e ne restituisce il nome nella forma:
        'Event [Nome] As [Tipo]
        Private Function GetEventInfo(ByVal EI As EventInfo) As String
            Dim CompleteName As New StringBuilder

            CompleteName.AppendFormat("Event {0} As {1}", EI.Name, _
            EI.EventHandlerType.Name)

            Return CompleteName.ToString
        End Function

        'Funzione che suddivide il membro in base al tipo
        Private Function GetMemberInfo(ByVal Member As MemberInfo) _ 
            As String
            Dim Temp As String = ""

            If TypeOf Member Is MethodInfo Then
                Temp = GetMethodInfo(Member)
            ElseIf TypeOf Member Is FieldInfo Then
                Temp = GetFieldInfo(Member)
            ElseIf TypeOf Member Is PropertyInfo Then
                Temp = GetPropertyInfo(Member)
            ElseIf TypeOf Member Is EventInfo Then
                Temp = GetEventInfo(Member)
            End If

            Return Temp
        End Function

        'Analizza tutti i tipi, e con essi i sottotipi e i membri
        Public Overloads Sub Scan()
            If Me.Assembly Is Nothing Then
                Throw New NullReferenceException
            End If

            For Each Ty As Type In Me.Assembly.GetTypes
                Console.WriteLine("+ " & GetTypeInfo(Ty))
                For Each NestedTy As Type In Ty.GetNestedTypes
                    Scan(NestedTy, 1)
                Next
                'Si enumerano solo i membri senza nomi speciali e
                'che non sono tipi nidificati, poichè quelli sono
                'goà stati analizzati
                For Each Member As MemberInfo In Ty.GetMembers()
                    If Not IsSpecialName(Member) And _
                    Not Member.MemberType = MemberTypes.NestedType Then
                        Console.WriteLine("| + {0}", _ 
                            GetMemberInfo(Member))
                    End If
                Next
            Next
        End Sub

        'Dato che andremo a scrivere a schermo le informazioni con 
        'una struttura ad albero, sarebbe bello anche visualizzarla 
        'simulandola con dei
        'trattini:
        '+ Tipo1
        '| + Tipo2
        '|   + Tipo3
        'eccetera...
        Private Overloads Sub Scan(ByVal NestedType As Type, _
            ByVal Level As Int16)
            'Il costruttore New del tipo String accetta 2 parametri: 
            'il primo è un carattere, e il secondo il numero di 
            'volte per cui ripeterlo
            Console.WriteLine("|{0}+ {1}", New String(" ", Level * 2 - 1), _
              GetTypeInfo(NestedType))
            'Si addentra ricorsivamente nel ciclo
            For Each NestedTy As Type In NestedType.GetNestedTypes
                Scan(NestedTy, Level + 1)
            Next
            For Each Member As MemberInfo In NestedType.GetMembers()
                If Not IsSpecialName(Member) And _
                Not Member.MemberType = MemberTypes.NestedType Then
                    Console.WriteLine("|{0}+ {1}", _ 
                    New String(" ", Level * 2 + 1), GetMemberInfo(Member))
                End If
            Next
        End Sub

        'Questa procedura scansiona un solo tipo
        Public Sub ScanType(ByVal T As Type)
            Scan(T, 1)
        End Sub
    End Class

    Sub Main()
        Dim M As New AssemblyScanner(Assembly.GetExecutingAssembly)

        M.Scan()

        Console.ReadKey()
    End Sub
End Module  
Qui potete scaricare un file di testo con la reflection del mio modulo, che comprende tutte le classe fino ad ora create negli esempi.




 

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