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:
  • ParameterInfo
  • MemberInfo
  • ConstructorInfo

Em System.Type temos:

  • Enum
  • Type

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


Module Module1

   Sub Main()

          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
do método como parâmetro.

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:


José Carlos Macoratti