VB .NET - Usando Reflection
A plataforma .NET introduziu um requisito importante para o desenvolvimento de aplicações: cada unidade de distribuição de código (assembly) agora exige um arquivo de metadados para descrever a si mesmo e os tipos que são definidos no assembly.
Abaixo vemos a figura que mostra como esse tipo de metadados e as informações que estão contidas dentro de um assembly compilado.
Então, na plataforma . NET, quer seja usando C# ou VB .NET, o compilador gera um executável Win32-portátil(PE), que compreende principalmente MSIL e metadados.
Assim , um Assembly é um arquivo do tipo DLL ou EXE e um Manifest é uma descrição detalhada (metadados) de um assembly.
Um arquivo .exe ou .dll é formado basicamente por metadados e IL - Intermediate Language - que a linguagem intermediária entre o .NET framework e código de máquina.
IL - Intermediate
Language ou MSIL
Se você ainda não se deu conta, na plataforma .NET quando você efetua a compilação do seu código ele não é compilado diretamente para linguagem de máquina. (A IL é um conjunto de instruções em um tipo de assembly independente da CPU.) Todas as linguagens .NET efetuam a compilação para a Microsoft Intermediary Language (MSIL ou apenas IL). A IL é então compilada em tempo de execução. Esta operação feita em duas etapas tem algumas vantagens como por exemplo: o fato de você estar apto a consultar dinamicamente um assembly por um tipo ou um método usando a reflection. Como nada é perfeito, este processo tem um efeito colateral : torna possível, usando a engenharia reversa, ter acesso a seu algoritmo ou lógica de negócio. Podemos fazer isso usando a ferramenta MSIL Disassembler ou IL Dasm que é instalada com a .NET Framework SDK e que pode ser encontrada clicando em: Iniciar -> Todos os Programas -> Microsoft Visual Studio 2010 -> Microsoft Windows SDK Tools -> IL Disassembler(x64) |
Com base nessa característica, um recurso muito poderoso da plataforma . NET, é que ela permite que você escreva código para acessar metadados de um aplicativo por meio de um processo conhecido como Reflection.
Reflection é o ato de inspecionar via programação um assembly (um executável ou dll), seus metadados e o tipo de informação que está contida dentro dele, ou seja, é a capacidade de descobrir o tipo de informação em tempo de execução.
Usando Reflection podemos ler os metadados de um programa formato .exe ou .dll que esta sendo executado ou podemos acessar um arquivo .exe ou .dll em outra biblioteca. Fazemos isso usando a API Reflection.
A API Reflection permite obter informações sobre assemblies e os tipos definidos, permite também criar, invocar e acessar instâncias de tipos em tempo de execução via código e também coletar e manipular informações sobre ela própria podendo ser usada para efetivamente encontrar todos os tipos em um assembly e invocar métodos no assembly. Em tempo de execução o mecanismos Reflection utiliza o arquivo PE (Portable Executable) para ler informações sobre o assembly permitindo usar código que não esta disponível em tempo de compilação.
Dessa forma a API Reflection é conjunto de classes, parte da qual mostramos na figura abaixo, definidas no namespace System.Reflection. Esse namespace contém classes e interfaces através das quais podemos explorar os tipos e métodos para obter informações sobre os assemblies e tipos.
Além desses temos os seguinte tipos no namespace
System.Reflection:
Em System.Type temos:
Obs:O método GetType() pode ser usado para obter diversas informações. |
Uma das principais classes usadas é a classe System.Type que pode ser usada para obter as seguintes informações:
Com reflection podemos usar o late binding que consiste em carregar os módulos de um assembly a partir de parâmetros que somente serão conhecidos em tempo de execução da aplicação através dos métodos: Load, LoadFrom, LoadWithPartialName, etc.
A seguir uma breve descrição dos principais métodos estáticos da classe Assembly:
Nome | Descrição |
GetAssembly | Retorna um Assembly que contém um tipo específico |
GetCallingAssembly | Retorna o Assembly que contém o código que chamou o método atual |
GetEntryAssembly | Retorna o Assembly que contém o código que iniciou o processo atual |
GetExecutingAssembly | Retorna o Assembly que contém o código atualmente em execução |
Load | Carrega um Assembly na para o atual AppDomain |
LoadFile | carrega um assembly especificando o caminho |
LoadFrom | Carrega um assembly para o atual AppDomain localizado em um caminho específico |
ReflectionOnlyLoad | Carrega um Assembly, mas permite apenas o interrogatório do Assembly e não a sua execução |
ReflectionOnlyLoadFrom | Carrega um Assembly localizado em um caminho específico e permite apenas o interrogatório do Assembly e não a sua execução |
Usando Reflection - Tarefas básicas
Para você sentir o gostinho do Reflection vamos começar escrevendo um código bem simples que le informações sobre um módulo do programa atual em execução.
Abra o Visual Studio 2012 Express for desktop e crie um novo projeto do tipo Console Application com o nome Reflection_Intro;
A seguir inclua o código abaixo no módulo Module1:
Imports System.Reflection
Dim assbly As Assembly = [Assembly].GetExecutingAssembly() Console.WriteLine("Nome Completo do assembly : " & assbly.FullName) Console.WriteLine("Localização do assembly : " & assbly.Location) Console.ReadKey() End Sub End Module |
No código criamos uma referência ao objeto usando o método GetExecutingAssembly que obtém o assembly que contém o código que está em execução atualmente.
A propriedade FullName exibe o nome completo do programa incluindo a versão e informações sobre a cultura.
A propriedade Location mostra o caminho completo do arquivo carregado que contém o manifesto.
A seguir vamos criar outro exemplo simples usando Reflection que mostra como inspecionar os metadados associados a um assembly. Por exemplo, digamos que você precisa determinar via código o nome de um assembly. Podemos começar criando e inicializando um objeto da classe Assembly.
Abra o Visual Studio 2012 Express for desktop e clique em New Project, selecionando Visual Basic e o template Console Application e informando o nome UsandoReflectionVBNET;
A seguir inclua o código baixo no módulo Module1:
Imports System.Reflection Module Module1 Sub Main() Dim AssemblyPath As String AssemblyPath = "C:\_vbn\ImageViewerVB\BNET\\bin\Debug\ImageViewerVB.dll" '*** cria e inicializa um objeto assembly e então consuulta o nome Dim asm As [Assembly] = [Assembly].LoadFrom(AssemblyPath) Console.WriteLine("Nome do Assembly: " & asm.FullName) Console.ReadKey() End Sub End Module |
Como vamos usar Reflection temos que declarar o namespace System.Reflection.
A seguir definimos o nome e caminho de um arquivo assembly, no caso um arquivo DLL qualquer, especificado como: "C:\_vbn\ImageViewerVB\ImagemViewerVB\bin\Debug\ImageViewerVB.dll"
A classe Assembly fornece um método estático chamado LoadFrom que torna possível inicializar um objeto Assembly utilizando o caminho físico de um arquivo assembly. Uma vez iniciado o objeto Assembly, você pode consultar o seu nome completo usando a propriedade FullName.
Você deve ter notado que o nome Assembly aparece dentro de colchetes. Estes colchetes são necessários na sintaxe no Visual Basic .NET porque Assembly também é uma palavra-chave. Você precisa usar os colchetes para informar ao compilador VB que Assembly está sendo usado como um nome de classe, em vez de uma palavra-chave.
Vejamos outro exemplo onde iremos usar a classe System.Type para obter o tipo Integer para descrever o seu tipo na plataforma .NET.
A classe System.Type é uma classe abstrata(não pode ser instanciada) que representa um tipo na Common Type System (CTS) e permite consultar o nome do tipo, módulo e namespace abrangente do tipo, e se o tipo é um tipo de valor ou um tipo de referência.
Imports System.Reflection Module Module1 Sub Main() Dim i As Integer = 100 Dim t As Type = i.GetType() Console.WriteLine("tipo do objeto integer : " & t.Name) Console.ReadKey() End Sub End Module |
Ainda usando a classe Type podemos obter informações sobre o tipo disponível através do Framework. Quando você quer descobrir via código a informação de um tipo, você tem que obter uma instância da classe System.Type que foi inicializada utilizando um tipo específico que você deseja inspecionar. Um objeto assembly permite enumerar todos os seus tipos de nível superior usando um simples laço For Each para inspecionar objetos System.Type um por um:
Abaixo temos o código onde a rotina ExibirTipos() recebe um Assembly e exibe todos os tipos :
Imports System.Reflection Module Module1 Sub Main() Dim AssemblyPath As String = "C:\__VBN\ImageViewerVB.dll" '*** cria e inicializa um objeto assembly e então consuulta o nome Dim asm As [Assembly] = [Assembly].LoadFrom(AssemblyPath) ExibirTipos(asm) Console.ReadKey() End Sub Public Sub ExibirTipos(ByVal asm As [Assembly]) For Each t As System.Type In asm.GetTypes Console.WriteLine(t.Name) Next End Sub End Module |
Obtendo detalhes internos de um Assembly com Reflection
Vamos adicionar um outro projeto projeto do tipo Console Application ao projeto no menu File->Add Project para retornar os tipos, métodos e tipos de retorno de um assembly.
No método Sub Main() defina o código abaixo:
Imports System.Reflection Module Module1 Sub Main() Dim asmbly As [Assembly] = Assembly.LoadFrom("C:\__VBN\ImageViewerVB.dll") Dim types As Type() = asmbly.GetTypes() For Each t As Type In types If t.IsClass Then Console.WriteLine("Nome da Classe : " + t.FullName) Console.WriteLine() End If Dim nomeMetodo As MethodInfo() = t.GetMethods() For Each m As MethodInfo In nomeMetodo Console.WriteLine("Nome Método :" + m.Name) Console.WriteLine("Tipo Retorno Método : " & m.ReturnType.ToString) If m.Name = "Main" Then Dim obj As Object = Activator.CreateInstance(t) Dim myarray As String() = {} m.Invoke(obj, myarray) Console.WriteLine() End If Next Next Console.ReadKey() End Sub End Module |
No código acima primeiro carregamos o assembly a ser estudado usando o método estático LoadFrom da classe Assembly.
A seguir percorremos os tipos e verificamos se o mesmo é uma classe(IsClass) ou método e exibimos o nome do método e o seu tipo de retorno.
Note que estamos criando uma instância da classe com : Dim obj As Object = Activator.CreateInstance(t)
Depois definimos um array de strings vazio: Dim myarray As String() = {}
A seguir temos:
m: referência do método;
obj: referência da classe;
myarray : representa o método sem argumentos
Obtendo as propriedades e os valores de um objeto com Reflection
Vamos agora adicionar um novo projeto Windows Forms ao nosso projeto para listar as propriedades de um objeto e seus valores. Vamos usar o método GetProperties de um tipo para obter informações das propriedades e a seguir vamos percorrer as informações das propriedades exibindo o nome de cada propriedade, tipo de dados e valor.
Vamos usar um controle ListView com três colunas o cabeçalho: Propriedade, tipo e valor.
Vamos examinar o tipo Form1 e chamar seu método GetProperties para obter uma matriz de objetos PropertyInfo descrevendo as propriedades.
No Visual Studio 2012 Express for desktop no menu File clique em Add-> Project e inclua um novo projeto do tipo Windows Forms Application com o nome Reflection_Properties;
A seguir inclua no formulário form1.vb criado por padrão um controle ListView e defina as seguintes propriedades para este controle:
Abaixo temos o leiaute do formulário:
Agora no evento Load do formulário inclua o código a seguir:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Cria os cabeçalhos das colunas lvPropriedades.Columns.Clear() lvPropriedades.Columns.Add("Propriedade", 10, HorizontalAlignment.Left) lvPropriedades.Columns.Add("Tipo", 10, HorizontalAlignment.Left) lvPropriedades.Columns.Add("Valor", 10, HorizontalAlignment.Left) ' Lista as propriedades usando a classe que você deseja ao invés da classe Form Dim propriedade_valor As Object Dim propriedades_info As PropertyInfo() = GetType(Form1).GetProperties() lvPropriedades.Items.Clear() For i As Integer = 0 To propriedades_info.Length - 1 With propriedades_info(i) If .GetIndexParameters().Length = 0 Then propriedade_valor = .GetValue(Me, Nothing) If propriedade_valor Is Nothing Then ListViewCriaLinha(lvPropriedades, .Name, .PropertyType.ToString, "<Nothing>") Else ListViewCriaLinha(lvPropriedades, .Name, .PropertyType.ToString, propriedade_valor.ToString) End If Else ListViewCriaLinha(lvPropriedades, .Name, .PropertyType.ToString, "<array>") End If End With Next i ' Ajust as colunhas pasra adequar os dados lvPropriedades.Columns(0).Width = -2 lvPropriedades.Columns(1).Width = -2 lvPropriedades.Columns(2).Width = -2 End Sub |
Para cada um dos objetos da classe Form, o código examina a matriz GetIndexParameters para ver se a propriedade requer parâmetros. Se a propriedade não precisar de parâmetros, o programa usa GetValue para obter o objeto que contém o valor da propriedade. O código exibe o nome da propriedade, o tipo e exibe um valor vazio ("") se a propriedade não tem valor (a propriedade object é Nothing se a propriedade tem o valor Nothing) ou valor da string do objeto.
Se GetIndexParameters indicar que a propriedade requer parâmetros, o programa simplesmente exibe o nome da propriedade, o tipo e o valor "".
Depois de ter criado uma linha no ListView para cada propriedade, o código ajusta o tamanho das colunas do ListView para atender os seus dados.
A rotina ListVieweCriaLinha() possui o seguinte código:
Private Sub ListViewCriaLinha(ByVal lvw As ListView, ByVal item_titulo As String, ByVal ParamArray subitem_titulos() As String) ' cria um novo item Dim novo_item As ListViewItem = lvw.Items.Add(item_titulo) ' cria os sub-items. For i As Integer = subitem_titulos.GetLowerBound(0) To subitem_titulos.GetUpperBound(0) novo_item.SubItems.Add(subitem_titulos(i)) Next i End Sub |
Executando o projeto iremos obter a lista de propriedades, tipos e valores para a classe Form:
Invocando métodos dinamicamente
Um dos recursos mais usados do Reflection é a invocação dinâmica de métodos de um Assembly. A seguir temos um exemplo bem simples de como invocar um método de uma instância.
Crie um novo projeto do tipo Console Application no Visual Studio 2012 Express for desktop com o nome Reflection_InvocandoMetodos:
A seguir crie uma classe chamada Aviao com o seguinte código:
Public Class Aviao Public Property Nome() As String Public Property Velocidade() As Integer Public Sub ExibeNome() Console.WriteLine("Nome do Aviao é " + Nome) End Sub Public Sub Decolar() Console.WriteLine(Nome + " Esta decolando...(Velocidade:" + Velocidade + " km/h)") End Sub Public Sub Aterrisar() Console.WriteLine(Nome + " Aterrisou com sucesso") End Sub End Class |
Nesta classe definimos 3 métodos: ExibeNome, Decolar e Aterrisar e duas propriedades: Nome e Velocidade.
A seguir vamos criar um método chamado InvocaMetodo() passando a instância da classe e o nome do método:
Private Sub invocaMetodo(instancia As Object, nomeMetodo As String) 'obtem a informação do método usando o método info class Dim mi As MethodInfo = instancia.[GetType]().GetMethod(nomeMetodo) 'invocando o método 'null- sem parametro para a funcao mi.Invoke(instancia, Nothing) End Sub |
A classe MethodInfo é usada para acessar um método específico da instância da classe. Os métodos podem ser invocados mesmo se possuírem parâmetros.(Isso é muito útil quando usamos late binding)
Finalmente no método Main() vamos criar uma instância da classe Aviao e chamar a rotina para invocar os métodos da classe pelo nome usando Reflection:
Sub Main() Dim oAviao As New Aviao oAviao.Nome = "Titanic" oAviao.Velocidade = 500 'invoca os métodos invocaMetodo(oAviao, "ExibeNome") invocaMetodo(oAviao, "Decolar") invocaMetodo(oAviao, "Aterrisar") Console.ReadKey() End Sub |
Criamos a instância da classe Aviao e atribuímos valores às propriedades e a seguir chamamos o método invocaMetodo() para invocar os métodos da instância.
Abaixo temos o código completo e o resultado da execução do projeto :
Imports System.Reflection Module Module1 Sub Main() Dim oAviao As New Aviao oAviao.Nome = "Titanic" oAviao.Velocidade = 500 'invoca os métodos invocaMetodo(oAviao, "ExibeNome") invocaMetodo(oAviao, "Decolar") invocaMetodo(oAviao, "Aterrisar") Console.ReadKey() End Sub Private Sub invocaMetodo(instancia As Object, nomeMetodo As String) 'obtem a informação do método usando o método info class Dim mi As MethodInfo = instancia.[GetType]().GetMethod(nomeMetodo) 'invocando o método 'null- sem parametro para a funcao mi.Invoke(instancia, Nothing) End Sub Public Class Aviao Public Property Nome() As String Public Property Velocidade() As Integer Public Sub ExibeNome() Console.WriteLine("Nome do Aviao é " + Nome) End Sub Public Sub Decolar() Console.WriteLine(Nome + " Esta decolando...(Velocidade:" + Velocidade.ToString + " km/h)") End Sub Public Sub Aterrisar() Console.WriteLine(Nome + " Aterrisou com sucesso") End Sub End Class End Module |
Invocamos os 3 métodos pelo nome
passando a string com o nome |
Aguarde em breve mais artigos sobre Reflection.
Pegue o projeto completo aqui: UsandoReflectionVBNET.zip
Mat 8:24
E eis que se levantou no mar tão grande tempestade que o barco era coberto pelas ondas; ele, porém, estava dormindo.Mat 8:25
Os discípulos, pois, aproximando-se, o despertaram, dizendo: Salva-nos, Senhor, que estamos perecendo.Mat 8:26
Ele lhes respondeu: Por que temeis, homens de pouca fé? Então, levantando-se repreendeu os ventos e o mar, e seguiu-se grande bonança.Mat 8:27
E aqueles homens se maravilharam, dizendo: Que homem é este, que até os ventos e o mar lhe obedecem?
Gostou ?
Compartilhe no Facebook
Compartilhe no Twitter
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#