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`2Infatti, 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:
Apparirà una schermata come questa:Module Module1Sub EnumerateGenerics(ByVal AsmAs Assembly)For Each TAs TypeIn Asm.GetTypes 'Controlla se è generic If T.IsGenericTypeDefinitionThen 'Nome del tipo Dim NameAs 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 GenTAs TypeIn T.GetGenericArguments 'Se il parametro non è il primo, lo separa dal 'precedente con una virgola If GenT.GenericParameterPosition > 0Then Name &= ", "End If 'Quindi vi aggiunge il nome Name &= GenT.NameNext '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 AsmAs Assembly = AppDomain.CurrentDomain.GetAssemblies()(0) EnumerateGenerics(Asm) Console.ReadKey()End Sub End Module
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
'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: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 AAs T1,ByVal BAs T2) '... End Sub 'Metodo normale in classe generic: niente di speciale Public Sub BSub(ByVal CAs Int32) '... End Sub End Class 'Metodo generic da solo Public Sub CSub(Of K1, K2)(ByRef AAs K1,ByRef BAs K2) '... End Sub
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.Public LAs New List(Of Int32)Public IAs Int32
Module Module1 'Enumera solo i campi generic di un tipo Sub EnumerateGenericFieldMembers(ByVal TAs Type)For Each FAs FieldInfoIn T.GetFields() 'Console.WriteLine(F.FieldType) If F.FieldType.IsGenericTypeThen Dim GenericNameAs String Dim ThisTypeAs Type = F.FieldTypeDim IAs Int16 = 0 GenericName = ThisType.FullName.Remove(_ ThisType.FullName.IndexOf("`")) GenericName &= "(Of "For Each GenPAs TypeIn ThisType.GetGenericArguments 'Dato che non si stanno analizzando dei parametri 'generic, non si può utilizzare la proprietà 'GenericParameterPosition If I > 0Then GenericName &= ", "End If GenericName &= GenP.Name I += 1Next GenericName &= ")" Console.WriteLine("Dim {0}As {1}", F.Name, GenericName)End If Next End Sub Public LAs New List(Of Integer)Public IAs Int32Sub 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:
Qui potete scaricare un file di testo con la reflection del mio modulo, che comprende tutte le classe fino ad ora create negli esempi.Module Module1Class AssemblyScannerPrivate _AssemblyAs Assembly 'L'assembly da analizzare Public Property Assembly()As AssemblyGet Return _AssemblyEnd Get Set (ByVal ValueAs Assembly)If ValueIsNot Nothing Then _Assembly = ValueEnd If End Set End Property Sub New (ByVal AsmAs Assembly)Me .Assembly = AsmEnd 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 MIAs Object )As String Dim ScopeAs String If MI.IsAssemblyThen Scope = "Friend"ElseIf MI.IsFamilyThen Scope = "Protected"ElseIf MI.IsFamilyOrAssemblyThen Scope = "Protected Friend"ElseIf MI.IsPrivateThen Scope = "Private"ElseIf MI.IsPublicThen Scope = "Public"End If Return ScopeEnd 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 MemberAs Object )As Boolean Return Member.IsSpecialNameEnd 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 TAs Type)As String Dim FullNameAs New StringBuilderIf T.IsGenericTypeAnd T.Name.IndexOf("`") >= 0Then Dim PositionAs Int16 = 0 FullName.Append(T.Name.Remove(T.Name.IndexOf("`"))) FullName.Append("(Of ")For Each GenArgAs TypeIn T.GetGenericArgumentsIf Position > 0Then 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.ToStringEnd 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 TAs Type)As String Dim CompleteNameAs New StringBuilder 'Non si può utilizzare la funzione GetScope poichè le 'proprietà di Type inerenti all'ambito di visibilità 'hanno nome diverso If T.IsNestedThen 'Scope di tipi nidificati If T.IsNestedAssemblyThen CompleteName.Append("Friend ")ElseIf T.IsNestedFamilyThen CompleteName.Append("Protected ")ElseIf T.IsNestedFamORAssemThen CompleteName.Append("ProtectedFriend ")ElseIf T.IsNestedPrivateThen CompleteName.Append("Private ")ElseIf T.IsNestedPublicThen CompleteName.Append("Public ")End If Else 'Scope di tipi non nidificati If T.IsPublicThen CompleteName.Append("Public ")ElseIf T.IsNotPublicThen '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.IsClassThen If T.IsAbstractThen CompleteName.Append("MustInherit ")ElseIf T.IsSealedThen CompleteName.Append("NotInheritable ")End If End If 'Categoria del tipo If T.IsClassThen CompleteName.Append("Class ")ElseIf T.IsEnumThen CompleteName.Append("Enum ")ElseIf T.IsInterfaceThen CompleteName.Append("Interface ")ElseIf T.IsValueTypeThen '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.IsGenericTypeDefinitionThen Dim PositionAs Int16 = 0 CompleteName.Append("(Of ")For Each GenArgAs TypeIn T.GetGenericArgumentsIf Position > 0Then CompleteName.Append(", ")End If CompleteName.Append(GenArg.Name) Position += 1Next CompleteName.Append(")")End If Return CompleteName.ToStringEnd 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 MIAs MethodInfo)As String Dim CompleteNameAs 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.IsStaticThen CompleteName.Append("Shared ")End If 'Modificatori If MI.IsAbstractThen CompleteName.Append("MustOverride ")ElseIf MI.IsFinalThen 'Niente ElseIf MI.IsVirtualThen CompleteName.Append("Overridable ")End If 'Categoria If MI.ReturnTypeIsNot GetType(Void)Then CompleteName.Append("Function ")Else CompleteName.Append("Sub ")End If 'Se e'un costruttore oppure no If MI.IsConstructorThen CompleteName.Append("New")Else CompleteName.Append(MI.Name)End If 'Signature Dim PositionAs Int16 = 0 'Potrebbe essere un metodo generic If MI.IsGenericMethodDefinitionThen CompleteName.Append("(Of ")For Each GenArgAs TypeIn MI.GetGenericArgumentsIf Position > 0Then CompleteName.Append(", ")End If CompleteName.Append(GenArg.Name) Position += 1Next CompleteName.Append(")") Position = 0End If CompleteName.Append("(")For Each ArgTyAs ParameterInfoIn MI.GetParametersIf Position > 0Then CompleteName.Append(", ")End If If ArgTy.ParameterType.IsByRefThen CompleteName.Append("ByRef ")Else CompleteName.Append("ByVal ")End If If ArgTy.IsOptionalThen CompleteName.Append("Optional ")End If CompleteName.Append(ArgTy.Name) CompleteName.AppendFormat("As {0}", _ GetTypeName(ArgTy.ParameterType))If ArgTy.IsOptionalThen CompleteName.AppendFormat(" = {0}", _ ArgTy.DefaultValue.ToString)End If Position += 1Next CompleteName.Append(")")If MI.ReturnTypeIsNot GetType(Void)Then CompleteName.AppendFormat("As {0}", _ GetTypeName(MI.ReturnType))End If Return CompleteName.ToStringEnd Function 'Analizza un campo e ne restituisce il nome nella forma: '(Const)/[Scope] [Modifier] [Nome] As [Tipo] (= [Costante]) Private Function GetFieldInfo(ByVal FIAs FieldInfo)As String Dim CompleteNameAs New StringBuilder CompleteName.Append(GetScope(FI) & " ") 'Costante If FI.IsLiteralThen CompleteName.Append("Const ")ElseIf FI.IsInitOnlyThen CompleteName.Append("ReadOnly ")End If CompleteName.Append(FI.Name & " ") CompleteName.AppendFormat("As {0}", GetTypeName(FI.FieldType))If FI.IsLiteralThen CompleteName.AppendFormat(" = {0}", FI.GetRawConstantValue)End If Return CompleteName.ToStringEnd Function 'Analizza una proprietà e ne restituisce il nome: '[Modifier] Property [Nome][Signature] Private Function GetPropertyInfo(ByVal PIAs PropertyInfo)As String Dim CompleteNameAs New StringBuilderIf PI.CanReadThen If PI.CanWriteThen 'Niente Else CompleteName.Append("ReadOnly ")End If Else CompleteName.Append("WriteOnly ")End If CompleteName.Append("Property ") CompleteName.Append(PI.Name) 'Signature Dim PositionAs Int16 = 0 CompleteName.Append("(")For Each ArgAs ParameterInfoIn PI.GetIndexParametersIf Position > 0Then CompleteName.Append(", ")End If CompleteName.Append("ByVal ")If Arg.IsOptionalThen CompleteName.Append("Optional ")End If CompleteName.Append(Arg.Name) CompleteName.AppendFormat("As {0}", _ GetTypeName(Arg.ParameterType))If Arg.IsOptionalThen CompleteName.AppendFormat(" = {0}", _ Arg.DefaultValue.ToString)End If Next CompleteName.AppendFormat(")As {0}", _ GetTypeName(PI.PropertyType))Return CompleteName.ToStringEnd Function 'Analizza un evento e ne restituisce il nome nella forma: 'Event [Nome] As [Tipo] Private Function GetEventInfo(ByVal EIAs EventInfo)As String Dim CompleteNameAs New StringBuilder CompleteName.AppendFormat("Event {0}As {1}", EI.Name, _ EI.EventHandlerType.Name)Return CompleteName.ToStringEnd Function 'Funzione che suddivide il membro in base al tipo Private Function GetMemberInfo(ByVal MemberAs MemberInfo) _As String Dim TempAs String = ""If TypeOf MemberIs MethodInfoThen Temp = GetMethodInfo(Member)ElseIf TypeOf MemberIs FieldInfoThen Temp = GetFieldInfo(Member)ElseIf TypeOf MemberIs PropertyInfoThen Temp = GetPropertyInfo(Member)ElseIf TypeOf MemberIs EventInfoThen Temp = GetEventInfo(Member)End If Return TempEnd Function 'Analizza tutti i tipi, e con essi i sottotipi e i membri Public Overloads Sub Scan()If Me .AssemblyIs Nothing Then ThrowNew NullReferenceExceptionEnd If For Each TyAs TypeIn Me .Assembly.GetTypes Console.WriteLine("+ " & GetTypeInfo(Ty))For Each NestedTyAs TypeIn 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 MemberAs MemberInfoIn Ty.GetMembers()If Not IsSpecialName(Member)And _Not Member.MemberType = MemberTypes.NestedTypeThen 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 NestedTypeAs Type, _ByVal LevelAs 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 NestedTyAs TypeIn NestedType.GetNestedTypes Scan(NestedTy, Level + 1)Next For Each MemberAs MemberInfoIn NestedType.GetMembers()If Not IsSpecialName(Member)And _Not Member.MemberType = MemberTypes.NestedTypeThen 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 TAs Type) Scan(T, 1)End Sub End Class Sub Main()Dim MAs New AssemblyScanner(Assembly.GetExecutingAssembly) M.Scan() Console.ReadKey()End Sub End Module
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



