Guida al Visual Basic .NET
Capitolo 45° - La Reflection Parte II
La classe System.TypeLa classe Type è una classe davvero particolare, poiché rappresenta un tipo. Con tipo indichiamo tutte le possibili tipologie di dato esistenti: tipi base, enumeratori, strutture, classi e delegate. Per ogni tipo contemplato, esiste un corrispettivo oggetto Type che lo rappresenta: avevo detto all'inizio della guida, infatti, che ogni cosa in .NET è un oggetto, ed i tipi non fanno eccezione. Vi sorprenderebbe sapere tutto ciò che può essere rappresentato da una classe e fra poco vi svelerò un segreto... Ma per ora concentriamoci su Type. Questi oggetti rappresentanti un tipo - che possiamo chiamare per brevità OT (non è un termine tecnico) - vengono creati durante la fase di inizializzazione del programma e ne esiste una e una sola copia per ogni singolo tipo all'interno di un singolo AppDomain. Ciò significa che due contesti applicativi differenti avranno due OT diversi per rappresentare lo stesso tipo, ma non analizzeremo questa peculiare casistica. Ci limiteremo, invece, a studiare gli OT all'interno di un solo dominio applicativo, coincidente con il nostro programma.Come per gli assembly, esistono molteplici modi per ottenere un OT:
Module Module1 Sub Main() 'Ottiene un OT per il tipo double tramite 'l'operatore GetType Dim DoubleType As Type = GetType(Double) Console.WriteLine(DoubleType.FullName) 'Ottiene un OT per il tipo string tramite 'la funzione statica Type.GetType. Essa richiede 'come parametro il nome (possibilmente completo) 'del tipo. Nel caso il nome non corrisponda a 'nessun tipo, verrà restituito Nothing Dim StringType As Type = Type.GetType("System.String") Console.WriteLine(StringType.FullName) 'Ottiene un OT per il tipo ArrayList tramite 'la funzione d'istanza GetType. Da notare che, 'mentre le precedenti usavano come punto 'di partenza direttamente un tipo (o il suo nome), 'questa richiede un oggetto di quel tipo. Dim A As New ArrayList Dim ArrayListType As Type = A.GetType() Console.WriteLine(ArrayListType.FullName) Console.ReadKey() End Sub End ModuleOra che ho esemplificato come ottenere un OT, vorrei mostrare l'unicità di OT ottenuti in modi differenti: anche se usassimo tutti i tre metodi sopra menzionati per ottenere un OT per il tipo String, otterremo un riferimento allo stesso oggetto, poiché il tipo String è unico: Module Module1 Sub Main() Dim Type1 As Type = GetType(String) Dim Type2 As Type = Type.GetType("System.String") Dim Type3 As Type = "Ciao".GetType() Console.WriteLine(Type1 Is Type2) '> True Console.WriteLine(Type2 Is Type3) '> True 'Gli OT contenuti in Type1, Type2 e Type3 'SONO lo stesso oggetto Console.ReadKey() End Sub End ModuleQuesto non vale per il tipo System.Type stesso, poiché il metodo d'istanza GetType restituisce un oggetto RuntimeType. Questi dettagli, tuttavia, non vi interesseranno se non tra un bel po' di tempo, quindi possiamo anche evitare di soffermarci e procedere con la spiegazione. Ogni oggetto Type espone una quantità inimmaginabile di membri e penso che potrebbe essere la classe più ampia di tutto il Framework. Di questa massa enorme di informazioni, ve ne è un sottoinsieme che permette di sapere in che modo il tipo è stato dichiarato e quali sono le sue caratteristiche principali. Possiamo ricavare, ad esempio, gli specificatori di accesso, gli eventuali modificatori, possiamo sapere se si tratta di una classe, un enumeratore, una struttura o altro, e, nel primo caso, se è astratta o sigillata; possiamo sapere le sua classe base, le interfacce che implementa, se si tratta di un array o no, eccetera... Di seguito elenco i membri di questo sottoinsieme:
Module Module1 Sub EnumerateTypes(ByVal Asm As Assembly) Dim Category As String 'GetTypes restituisce un array di Type che 'indicano tutti i tipi definiti all'interno 'dell'assembly Asm For Each T As Type In Asm.GetTypes If T.IsClass Then Category = "Class" ElseIf T.IsInterface Then Category = "Interface" ElseIf T.IsEnum Then Category = "Enumerator" ElseIf T.IsValueType Then Category = "Structure" ElseIf T.IsPrimitive Then Category = "Base Type" End If Console.WriteLine("{0} ({1})", T.Name, Category) Next End Sub Sub Main() 'Ottiene un riferimento all'assembly in esecuzione, 'quindi al programma. Non otterrete molti tipi 'usando questo codice, a meno che il resto del 'modulo non sia pieno di codice vario come nel 'mio caso XD Dim Asm As Assembly = Assembly.GetExecutingAssembly() EnumerateTypes(Asm) Console.ReadKey() End Sub End Module Il nostro piccolo segretoPrima di procedere con l'enumerazione dei membri, vorrei mostrare che in realtà tutti i tipi sono classi, soltanto con regole "speciali" di ereditarietà e di sintassi. Questo codice rintraccia tutte le classi basi di un tipo, costruendone l'albero di ereditarietà fino alla radice (che sarà ovviamente System.Object):Module Module1 'Analizza l'albero di ereditarietà di un tipo Sub AnalyzeInheritance(ByVal T As Type) 'La proprietà BaseType restituisce la classe 'base da cui T è derivata If T.BaseType IsNot Nothing Then Console.WriteLine("> " & T.BaseType.FullName) AnalyzeInheritance(T.BaseType) End If End Sub Enum Status Enabled Disabled Standby End Enum Structure Example Dim A As Int32 End Structure Delegate Sub Sample() Sub Main() Console.WriteLine("Integer:") AnalyzeInheritance(GetType(Integer)) Console.WriteLine() Console.WriteLine("Enum Status:") AnalyzeInheritance(GetType(Status)) Console.WriteLine() Console.WriteLine("Structure Example:") AnalyzeInheritance(GetType(Example)) Console.WriteLine() Console.WriteLine("Delegate Sample:") AnalyzeInheritance(GetType(Sample)) Console.WriteLine() Console.ReadKey() End Sub End ModuleL'output mostra che il tipo Integer e la struttura Example derivano entrambi da System.ValueType, che a sua volta deriva da Object. La definizione rigorosa di "tipo value", quindi, sarebbe "qualsiasi tipo derivato da System.ValueType". Infatti, al pari dei primi due, anche l'enumeratore deriva indirettamente da tale classe, anche se mostra un passaggio in più, attraverso il tipo System.Enum. Allo stesso modo, il delegate Sample deriva dalla classe DelegateMulticast, la quale derivata da Delegate, la quale deriva da Object. La differenza sostanziale tra tipi value e reference, quindi, risiede nel fatto che i primi hanno almeno un passaggio di ereditarietà attraverso la classe System.ValueType, mentre i secondi derivano direttamente da Object. System.Enum e System.Delegate sono classi astratte che espongono utili metodi statici che potete ispezionare da soli (sono pochi e di facile comprensione). Ma ora che sapete che tutti i tipi sono classi, potete anche esplorare i membri esposti dai tipi base. Enumerare i membriFino ad ora abbiamo visto solo come analizzare i tipi, ma ogni tipo possiede anche dei membri (variabili, metodi, proprietà, eventi, eccetera...). La Reflection permette anche di ottenere informazioni sui membri di un tipo, e la classe in cui queste informazioni vengono poste è MemberInfo, del namespace System.Reflection. Dato che ci sono diverse categorie di membri, esistono altrettante classi derivate da MemberInfo che ci raccontano una storia tutta diversa a seconda di cosa stiamo guardando: MethodInfo contiene informazioni su un metodo, PropertyInfo su una proprietà, ParamterInfo su un parametro, FieldInfo su un campo e via dicendo. Fra le molteplici funzioni esposte da Type, ce ne sono alcune che servono proprio a reperire questi dati; eccone un elenco:
Module Module1 Sub Main() Dim T As Type = GetType(String) 'Elenca tutti i membri di String For Each M As MemberInfo In T.GetMembers 'La proprietà MemberType restituisce un enumeratore che 'specifica di che tipo sia il membro, se una proprietà, 'un metodo, un costruttore, eccetera... Console.WriteLine(M.MemberType.ToString & " " & M.Name) Next Console.ReadKey() End Sub End ModuleEseguendo il codice appena proposto, potrete notare che a schermo appaiono tutti i membri di String, ma molti sono ripetuti: questo si verifica perchè i metodi che possiedono delle varianti in overload vengono riportati tante volte quante sono le varianti; naturalemnte, ogni oggetto MethodInfo sarà diverso dagli altri per le informazioni sulla quantità e sul tipo di parametri passati a tale metodo. Accanto a questa stranezza, noterete, poi, che per ogni proprietà ci sono due metodi definiti come get_NomeProprietà e set_NomeProprietà: questi metodi vengono creati automaticamente quando il codice di una proprietà viene compilato, e vengono eseguiti al momento di impostare od ottenere il valore di tale proprietà. Altra stranezza è che tutti i costruttori si chiamano ".ctor" e non New. Stiamo cominciando ad entrare nel mondo dell'Intermediate Language, il linguaggio intermedio simil-macchina in cui vengono convertiti i sorgenti una volta compilati. Di fatto, noi stiamo eseguendo il processo inverso della compilazione, ossia la decompilazione. Alcune informazioni vengono manipolate nel passaggio da codice a IL, e quando si torna indietro, le si vede in altro modo, ma tutta l'informazione necessaria è ancora contenuta lì dentro. Non esiste, tuttavia, una classe già scritta che ritraduca in codice tutto il linguaggio intermedio: ciò che il Framework ci fornisce ci consente solo di conoscere "a pezzi" tutta l'informazione ivi contenuta, ma sottolineiamo "tutta". Sarebbe, quindi, possibile - ed infatti è già stato fatto - ritradurre tutti questi dati in codice sorgente. Per ora, ci limiteremo a "ricostruire" la signature di un metodo. Prima di procedere, vi fornisco un breve elenco dei membri significativi di ogni derivato di MemberInfo: MemberInfo
MethodInfo
FieldInfo
PropertyInfo
EventInfo (per ulteriori informazioni, vedere i capitoli della sezione B sugli eventi)
Module Module1 'Analizza il metodo rappresentato dall'oggetto MI 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 il capitolo "Magie con le stringhe" Dim CompleteName As New System.Text.StringBuilder 'Lo specificatore di accesso Dim Scope As String 'Gli eventuali modificatori Dim Modifier As String 'La categoria: 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. Quindi, 'definire un metodo senza modificatori polimorfici '(come quelli che seguono qua in basso), equivale a 'definirlo NotOverridable. Perciò non si 'aggiunge 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 tonda aperta. '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. IsByRef è un membro di Type, ma viene 'usato solo quando il tipo in questione indica il tipo 'di un parametro 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 Signature.Append(P.Name) If P.ParameterType.IsArray Then Signature.Append("()") 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(" As " & P.ParameterType.Name.Replace("[]","")) '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 &rt; Methods.Length Then AnalyzeMethod(Methods(Index)) End If Console.ReadKey() End Sub End ModuleAnalizzando il metodo Test, si otterrà questo output: 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
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|