Reflection é uma técnica que utilizamos no .NET para lidar diretamente com os metadados, ou seja, extrair quais são os campos, propriedades, métodos, eventos, atributos, etc., para que possamos criar um código dinâmico, com o intuito de inspecionar ou até mesmo alterar valores ou a execução baseado no que temos em cada um dos tipos.
Uma das técnicas utilizadas pelo próprio .NET Framework, e também por nós, é a criação ou utilização de atributos que nos permitem decorar em diversos membros, para que eles guiem a execução, permitindo ao consumidor destes tipos, a ler e interpretá-los de acordo com uma determinada regra. Para exemplificar, abaixo temos alguns casos de uso destas técnicas:
-
O Visual Studio utiliza atributos decorados nas propriedades das classes (controles), para exibir cada uma delas na janela de propriedades da IDE.
-
O WCF obriga a interface que define o contrato do serviço estar decorada com o atributo ServiceContractAttribute.
-
Utilizamos o atributo AuthorizeAttribute para refinar o acesso às ações dos controllers do ASP.NET MVC.
-
Alguma funcionalidade específica em vossa aplicação.
Partindo deste princípio, podemos ter um atributo customizado que utilizamos para decorar em propriedades de um determinado tipo. Esse atributo basicamente define um rótulo customizado para cada propriedade onde ele é aplicado. Abaixo temos o atributo e a classe já fazendo uso do mesmo:
public class Exibicao : Attribute
{
public string Label { get; set; }
}
public class LinhaDeRegistro
{
[Exibicao(Label = “Nome”)]
public string Nome { get; set; }
[Exibicao(Label = “Código”)]
public int Codigo { get; set; }
[Exibicao(Label = “Valor”)]
public decimal Valor { get; set; }
public DateTime DataDaOcorrencia { get; set; }
}
É importante dizer que o atributo por si só não serve para nada. Precisamos de um código que o consuma e faça alguma coisa com ele. Para isso, temos um método que percorre as propriedades do objeto, e para cada uma delas, verifica se existe o atributo recém criado, e se existir, exibe a informação formatada utilizando o rótulo que foi definido durante o desenvolvimento.
public static void ExibirColunas(Type type, object value)
{
foreach (var p in new type.GetProperties())
{
var a = p.GetCustomAttribute(typeof(Attribute)) as Exibicao;
if (a != null)
Console.WriteLine(
“{0}: {1}”, a.Label, p.GetValue(value, null));
}
}
Com este método criado, podemos fazer o uso dele passando o objeto contendo as informações e também o seu tipo. Apesar de conseguir extrair o Type no interior do próprio método, optei por deixar propositalmente via parâmetro, que mais tarde isso fará sentido. Abaixo o código que chama este método:
var linha = new LinhaDeRegistro()
{
Codigo = 1,
Nome = “Israel”,
Valor = 12000,
DataDaOcorrencia = DateTime.Now
};
ExibirColunas(linha.GetType(), linha);
Ao rodar a aplicação, o resultado será:
Nome: Israel
Código: 1
Valor: 12000
Como percebemos, o valor da propriedade DataDaOcorrencia não é exibida. Mas faz sentido, pois ela não foi decorada com o atributo Exibicao que criamos acima. Para resolver isso, basta, também, decorar o atributo na propriedade, compilar e rodar, que a informação será exibida conforme o esperado. Mas como faremos isso, se a classe LinhaDeRegistro está em um outro assembly, onde não temos acesso ao código fonte?
Para sanar esse problema, podemos recorrer à classe CustomReflectionContext, que está debaixo do namespace e assembly System.Reflection.Context.dll. Esse assembly faz parte do .NET Framework 4.5, que externaliza as capacidades de reflection de um determinado objeto, sem a necessidade de recriar um modelo total para isso.
A implementação padrão da classe abstrata CustomReflectionContext, simplesmente serve como um wrapper, sem qualquer mudança em relação ao tradicional. Mas isso nos dá a chance de customizar a criação de um novo contexto de reflection, e sobrescrevendo alguns métodos, nós podemos adicionar, remover ou alterar atributos ou até mesmo adicionar novas propriedades aos tipos que estamos manipulando.
Para exemplificar a sua utilização, criamos um contexto customizado, onde sobrescrevemos o método GetCustomAttributes, identificamos se o membro trata-se da propriedade DataDaOcorrencia, e se for, retornamos um atributo Exibicao configurado conforme é necessário por nossa aplicação (ou seja, pelo método ExibirColunas).
public class ContextoParaExibicao : CustomReflectionContext
{
protected override IEnumerable<object> GetCustomAttributes(MemberInfo member,
IEnumerable<object> declaredAttributes)
{
if (member.Name == “DataDaOcorrencia”)
return new[] { new Exibicao() { Label = “Data da Ocorrência” } };
return base.GetCustomAttributes(member, declaredAttributes);
}
}
Finalmente, depois da criação deste contexto, precisamos utilizá-lo, e para isso, o instanciamos e chamamos o método MapType, que recebe como parâmetro um objeto do tipo TypeInfo, que também novo no .NET 4.5, e caracteriza a parte de “reflection” de um determinado tipo. Como essa classe herda da classe Type, podemos utilizar o retorno deste método, e passar diretamente para o método consumidor, sem a necessidade de alterar nada em seu interior:
ExibirColunas(
new ContextoParaExibicao().MapType(linha.GetType().GetTypeInfo()),
linha);
Apesar de algumas soluções já existirem no .NET Framework (TypeDescriptor e PropertyDescriptor) para tentar extrair essas mesmas informações, provendo também tipos específicos para a customização da extração (ICustomTypeDescriptor), o time do .NET Framework/CLR decidiu incorporar isso diretamente no .NET Framework, o que pode ser utilizado por todos aqueles que desejam essa funcionalidade, de dentro ou fora da Microsoft.