C # - Reflection e Metadata


A maioria dos programas são escritos para trabalhar com dados. Eles em geral, lêem, escrevem, manipulam e exibem dados. (Gráficos são um exemplo típico de programas desse tipo). Os tipos que você, como o programador, criar e usar são projetados para estes fins, e é você, em tempo de projeto, que deve compreender as características dos tipos que você usa.

Metadado

Para alguns tipos de programas, no entanto, os dados que eles manipulam não são números, textos ou gráficos mas são as informações sobre programas e tipos de programa. Essas informações são conhecidas como metadados.(Uma informação sobre a informação)

Em uma metáfora podemos pensar em Metadados como sendo o sangue da plataforma. NET. Muitos dos recursos que usamos todos os dias durante o tempo de projeto, durante o tempo de compilação, e durante o tempo de execução, se baseiam na presença de metadados. Os Metadados permitem a um Assembly e os tipos dentro do Assembly se auto-descreverem.

Assim , os metadados são informações sobre os dados, ou seja, informações sobre os tipos, o código, assembly e assim por diante - que é armazenada junto com seu programa.

Podemos adicionar metadados em seu código através dos atributos.

Atributos

Os Atributos são um mecanismo para a adição de metadados, tais como instruções do compilador e outros dados sobre seus dados, métodos e classes, para o próprio programa. Atributos são inseridos nos metadados e são visíveis através ILDasm e outras ferramentas para leitura de metadados.

Um atributo é um objeto que representa os dados que você deseja associar com um elemento no seu programa. O elemento ao qual você anexa um atributo é conhecido como o target (alvo) desse atributo.

Por exemplo, o atributo: [NoIDispatch]

Está associado a uma classe ou uma interface para indicar que a classe alvo deve derivar de IUnknown, em vez de IDispatch, ao exportar para COM.

Atributos podem ser de dois tipos: intrínsecos e personalizado.

Se você procurar na CLR, você vai encontrar um grande número de atributos. Alguns atributos são aplicados a uma assembly, outros a uma classe ou interface, e alguns, como o [WebMethod], a membros da classe. A seguir temos os principais os alvos de atributos:

Nome do Membro Uso
All Aplicado a qualquer um dos seguintes elementos: assembly, class, class member, delegate, enum, event, field, interface, method, module, parameter, property, return value, ou struct
Assembly Aplicado ao próprio assembly
Class Aplicado a instância de classes
ClassMembers Aplicado a classes, structs, enums, constructors, methods, properties, fields, events, delegates, e interfaces
Constructor Aplicado a um dado constructor
Delegate Aplicado a método delegado
Enum Aplicado a uma enumeração (enumeration)
Event Aplicado a um evento (event)
Field Aplicado a um campo (field)
Interface Aplicado a uma interface
Method Aplicado a um método
Module Aplicado a um único módulo
Parameter Aplicado a um parâmetro de um método
Property Aplicado a uma propriedade (get e set, se implementado)
ReturnValue Aplicado a um valor retornado
Struct Aplicado a um struct

Aplicando atributos

Você pode aplicar atributos para seus alvos, colocando-os entre colchetes imediatamente antes do item de destino. Você pode combinar atributos quer por empilhamento um sobre o outro:

[assembly: AssemblyDelaySign (false)]
[assembly: AssemblyKeyFile ("\\KeyFile.snk.")]

ou separando os atributos com vírgulas:

[assembly: AssemblyDelaySign (false), assembly: AssemblyKeyFile ("\\KeyFile.snk.")]

Reflection

Um programa pode olhar para os metadados de outros conjuntos ou de si mesmo, enquanto ele está executando. Quando um programa em execução olha para seus próprios metadados, ou de outros programas, a ação é chamada de Reflection.

Dessa forma, Reflection é o processo pelo qual um programa pode ler seus próprios metadados. Um programa que faz Reflection sobre si mesmo, extrai metadados de sua montagem e usa estes metadados ou para informar ao usuário ou para modificar o seu próprio comportamento.

Reflection é então a capacidade de ler metadados em tempo de execução. Usando a reflexão (Reflection), é possível descobrir os métodos, propriedades e eventos de um tipo, e então invocá-los dinamicamente. Usando Reflection podemos também criar novos tipos em tempo de execução.

Um Object Browser (Navegador de objetos) é um exemplo de um programa que exibe metadados. Ele pode ler assemblies e exibir os tipos que eles contêm, juntamente com todas as características e os membros.

Reflection geralmente começa com uma chamada para um método presente em todos os objetos do framework NET: GetType.

O método GetType é um membro da classe System.Object, e o método retorna uma instância de System.Type, e System.Type é a porta de entrada para os metadados.

A classe System.Type é na verdade derivada de outra classe importante de reflexão: a classe MemeberInfo do namespace System.Reflection.

A classe MemberInfo é uma classe base para muitas outras classes que descrevem as propriedades e métodos de um objeto, incluindo FieldInfo, MethodInfo, ConstructorInfo, ParameterInfo e EventInfo entre outros.

Como você pode suspeitar pelos seus nomes, você pode usar essas classes para inspecionar os diferentes aspectos de um objeto em tempo de execução.

Compreendendo Metadados de Assemblies

Quando você cria um executável com o Visual Basic, você constrói um assembly .NET. Um assembly é basicamente um recipiente de metadados e código. Os metadados são informações que a CLR usa para carregar e executar corretamente o assembly.

A Figura abaixo representa como um assembly é estruturado:

Observe que tipos dentro de um assembly podem ser agrupados em vários módulos. Um módulo é um container de tipos enquanto um assembly é um recipiente de módulos. Com reflection você pode inspecionar metadados e o código de um assembly usando código do Visual Basic, incluindo informações do assembly.

Obs: Quando se fala sobre assemblies, normalmente nos referimos a arquivos executáveis únicos. Assemblies podem ser compostos de vários arquivos vinculados; tenha em mente que os metadados de um assembly precisam residir apenas no assembly principal. Este é um caso especial e não pode ser realizado com o Visual Studio (você deve usar manualmente o MSBuild).

Praticando um pouco - preparando um Assembly de teste

Antes de mostrar as capacidades de Reflection, uma boa idéia é preparar um exemplo de código apropriado.

Primeiro, crie um novo projeto de biblioteca de classe com o nome Pessoa. O objetivo da biblioteca é expor uma implementação especial da classe Pessoa, com interfaces e implementações de enumerações para uma melhor demonstração de Reflection.

Abra o Visual Basic 2010 Express Edition e no menu File selecione New Project e a seguir escolha o template Class Library e informe o nome Pessoa e clique em OK;

Altere o nome da classe Class1.vb criada por padrão no projeto para Pessoa.vb e a seguir digite o código exibido a seguir :

Imports System.Text

Public Enum Generos
    Masculino = 0
    Feminino = 1
End Enum

Public Interface IPessoa
    Property Nome As String
    Property Sobrenome As String
    Property Idade As Integer
    Property Genero As Generos
    Event InstanciaCriada()
    Function CriarNomeCompleto() As String
End Interface

Public Class Pessoa
    Implements IPessoa

    Public Property Nome As String Implements IPessoa.Nome
    Public Property Genero As Generos Implements IPessoa.Genero
    Public Property Sobrenome As String Implements IPessoa.Sobrenome
    Public Property Idade As Integer Implements IPessoa.Idade
    Public Event InstanciaCriada() Implements IPessoa.InstanciaCriada

    Public Overridable Function CriarNomeCompleto() As String _
    Implements IPessoa.CriarNomeCompleto

        Dim nomeCompleto As New StringBuilder
        nomeCompleto.Append(Sobrenome)
        nomeCompleto.Append(" ")
        nomeCompleto.Append(Nome)
        nomeCompleto.Append(", ")
        nomeCompleto.Append(Genero.ToString)
        nomeCompleto.Append(", de idade ")
        nomeCompleto.Append(Idade.ToString)

        Return nomeCompleto.ToString
    End Function

End Class

Dê um Build no projeto (menu Debug-> Build Pessoa), e em seguida, adicione um projeto Console à solução atual através do menu File-> Add -> New Project, com o nome TesteReflection;

Por fim, adicione uma referência para a biblioteca de classes Pessoa de modo que, apenas para fins de demonstração, você possa carregar o assembly para a reflection, sem especificar o caminho completo.

Para isso no clique com o botão direito sobre o projeto console TesteReflection e selecione Add Reference e a seguir na janela Add Reference selecione o projeto Pessoa e clique em OK:

Obtendo informações de um Assembly

Podemos obter informações dos metadados de um assembly criando uma instância da classe System.Reflection.Assembly. Esta classe fornece membros estáticos e de instância para acessar informações de um assembly. Na tabela abaixo temos os métodos que em geral são usados para carregar um assembly e obter informações sobre seus metadados:

GetAssemblyCarrega um assembly contendo o tipo especificado
Método Descrição
GetExecutingAssembly Retorna a instância do assembly atual
GetEntryAssembly Retorna a instância do assembly que executa o processo atual
Load Carrega o assembly especificado no domínio da aplicação atual
LoadFile Carrega o assembly especificado a partir de um caminho definido
LoadFrom Carrega o assembly especificado no domínio da aplicação atual dado o caminho definido
ReflectionOnlyLoad Igual a Load , mas permite somente a inspeção Reflection e não a execução de código
ReflectionOnlyLoadFrom Igual a LoadFrom, mas permite somente a inspeção Reflection e não a execução de código

Quando você obtêm a instância do assembly o qual você deseja inspecionar você pode acessar a informação através de propriedades conforme mostrado no código a seguir que deve ser colocada no módulo do projeto console TesteReflection:

Altere o nome do módulo do projeto console TesteReflection de Module1.vb para getAssemblyInfo.vb e a seguir inclua o código abaixo no módulo:

Imports System.Reflection

Module getAssemblyInfo

    Sub Main()

        'Infere o namespace System.Reflection.Assembly
        Dim asm = Assembly.ReflectionOnlyLoadFrom("Pessoa.dll")

        With asm
            'Obtém o nome completo do assembly com versão e cultura
            Console.WriteLine("Nome do Assembly:")
            Console.WriteLine(.FullName)
            'Obtém informação se o assembly e confiável (trusted)
            Console.WriteLine("É confiável: {0}", .IsFullyTrusted)
            'Obtém o ponto de entrada do assembly; se vazio o construtor 
            'é o ponto de entrada
            Console.WriteLine("O método ponto de entrada é: {0}", .EntryPoint)
            'Obtém a versão da plataforma .NET no qual o assembly foi construído
            Console.WriteLine("Versão do Runtime: {0}", .ImageRuntimeVersion)
            'Informa se o assembly foi carregado do GAC
            Console.WriteLine("Carregado a partir do GAC: {0}", .GlobalAssemblyCache)
            'Obtém a localização do assembly
            Console.WriteLine("Caminho do Assembly : {0}", .Location)

            'Obtém um array de módulos carregado pelo assembly
            Console.WriteLine("Modulos carregados: ")
            For Each item As System.Reflection.Module In .GetLoadedModules
                Console.WriteLine(" {0}", item.Name)
            Next
        End With

        Console.ReadLine()
    End Sub
End Module

Observe como o código utilizar o método ReflectionOnlyLoadFrom para permitir somente a inspeção se poder executar código. Ao executar o projeto acima iremos obter:

Obs: Para poder executar o projeto TesteReflection clique sobre o mesmo com o botão direito do mouse e selecione Set as Startup Project.

- Observe que o número da versão RTM da plataforma .NET obtido: v4.0.30319

- O método Assembly.GetModules retorna um array dos módulos carregados pela instância do assembly : Pessoa.dll

Existem outros métodos interessantes que poderíamos ter usado como:

- GetExportedTypes o qual retorna um array dos tipos vísiveis publicamente ;
- GetFiles que retorna um array de objetos FileStream representando um arquivo no recurso do assembly

Até o momento estão usando Reflection para obter informação do assembly no primeiro nível o próximo passo é realizar a inspeção de tipos,mas isso é assunto para outro artigo, aguarde...

Pegue o projeto completo aqui: Pessoa_Reflection.zip

"E a ninguém na terra chameis vosso pai, porque um só é o vosso Pai, o qual esta nos céus."
"Nem vos chameis mestres, porque um só é o vosso Mestre, que é o Cristo." Mateus 23:9-10

Referências:


José Carlos Macoratti