Atenção: esse artigo tem um vídeo complementar. Clique e assista!

De que se trata o artigo

Neste arquivo serão expostos aspectos que envolvem a manipulação de arquivos-texto por soluções de software. A partir disto, serão apresentados exemplos que fazem uso de recursos de Reflection e de Attributes como um meio facilitador para este tipo de tarefa.


Em que situação o tema é útil

Reflection é um mecanismo do .NET Framework que permite a manipulação em tempo de execução de informações de metadados de tipos; permite ainda o acesso e a alteração de informações de estado (valores de propriedades, por exemplo) em instâncias de uma classe dinamicamente. Já o uso do recurso conhecido como Attributes possibilita, por sua vez, associar de maneira declarativa (e dentro do próprio código-fonte) informações capazes de definir aspectos do comportamento de tipos, métodos e propriedades; posteriormente, tais informações podem ser acessadas em runtime via Reflection.

O desenvolvimento de aplicações que se utilizam de arquivos texto posicionais tende muitas vezes a ser trabalhoso, sobretudo devido ao grande número de tipos de registro e à estrutura complexa destes últimos (contando com um grande número de campos, por exemplo). Conforme indicado neste artigo, o uso de Attributes em conjunto com o mecanismo de Reflection pode se constituir em um mecanismo que simplifique atividades deste gênero.

Geração de arquivos pré-formatados

Arquivos-texto pré-formatados ainda são um meio bastante comum de integração entre sistemas. Tais arquivos normalmente seguem um layout padrão constituído por diversos tipos de registros e campos. Logo, não é incomum que diversas aplicações voltadas para este meio sejam concebidas num modo pouco flexível a mudanças, com partes pouco coesas e que geram empecilhos para a execução de testes unitários. A solução discutida neste arquivo procura, através do uso de Attributes e Reflection, simplificar este tipo de desenvolvimento, bem como impedir que as dificuldades mencionadas se transformem em transtornos futuros.

Arquivos-texto posicionais representam um meio comum de troca de informações quando se leva em consideração a integração entre diferentes sistemas. Embora se trate de uma técnica em uso há algum tempo, principalmente em ambientes como mainframe, diversos órgãos governamentais e instituições financeiras ainda se valem deste tipo de prática.

Os padrões a seguir constituem exemplos bem conhecidos de arquivos-texto utilizados para a transferência de dados:

• CNAB: empregado no meio bancário para a troca (envio e retorno) de informações financeiras, com diversos bancos disponibilizando layouts para arquivos de 240 e 400 posições;

• SINTEGRA: padrão governamental que envolve o envio de dados relativos a operações de entrada e saída de mercadorias e serviços. Arquivos seguindo este tipo de layout são transmitidos para o Fisco do estado em que um contribuinte se encontra estabelecido;

• SPED: sigla do projeto Sistema Público de Escrituração Digital. Trata-se de uma exigência da Receita Federal em relação às empresas, englobando para isto arquivos que contêm informações relativas aos Livros Fiscais, Contábeis e de PIS / COFINS gerados pelas organizações;

• CAGED: Cadastro Geral de Empregados e Desempregados; constitui-se em uma obrigação das empresas perante o Ministério do Trabalho e Emprego. Este último, por sua vez, utiliza-se de tais informações para análises do mercado de trabalho e como instrumento na tomada de decisões para ações governamentais;

• RAIS: sigla de Relação Anual de Informações Sociais, sendo outro arquivo enviado por empresas ao Ministério do Trabalho e Emprego. Contém informações relacionadas ao quadro funcional da organização em questão.

É bastante normal que arquivos que sigam um layout específico sejam constituídos por diversos tipos de registros, com cada um destes últimos possuindo uma estrutura própria. Além disso, um determinado tipo de registro pode ser formado por um conjunto extenso de campos e envolver diversas regras para a formatação dos valores envolvidos.

Diante disto, não são incomuns cenários em que as soluções resultantes, embora atendam aos requisitos que originaram as mesmas, não sejam dotadas de meios que tornem o seu desenvolvimento e sua posterior manutenção mais flexíveis.

A solução demonstrada neste artigo procura fornecer meios que facilitem o desenvolvimento de aplicações voltadas à manipulação de arquivos. Para isto, será feito o uso de um conceito do .NET Framework conhecido como Attributes, juntamente com técnicas de Reflection. Outros recursos nativos a serem empregados serão Generics e LINQ To Objects, além do framework de persistência de dados NHibernate em conjunto com a extensão Fluent NHibernate.

Reflection: uma visão geral

Reflection é uma técnica disponibilizada pelo .NET Framework através da qual é possível o acesso ou, ainda, a manipulação de informações (metadatos) relativos a assemblies, classes, interfaces, propriedades, métodos, dentre outras estruturas de programação. É através da classe System.Type e de tipos definidos no namespace System.Reflection que são disponibilizados recursos para a utilização de informações de metadados.

Constituem exemplos de classes contendo informações de metadata e que encontram-se no namespace System.Reflection:

• PropertyInfo: disponibiliza dados referentes a uma propriedade que faça parte de uma determinada classe;

• MethodInfo: possibilita o acesso a informações que dizem respeito a um método de uma classe que se está analisando;

• EventInfo: permite o acesso ao metadados de um evento que conste na declaração de um determinado tipo;

• ConstructorInfo: retorna informações referentes a um construtor que faça parte de uma classe sob análise.

São tarefas possíveis de serem efetuadas por meio do mecanismo de Reflection:

• O acesso a informações de metadados de tipos e partes que constituam os mesmos;

• A descoberta de tipos que façam parte de um assembly;

• A construção de instâncias de classes a partir do acesso de informações dos construtores disponibilizados nas mesmas;

• A invocação dinâmica de métodos e propriedades de um objeto (característica esta conhecida como late binding);

• Criação de tipos customizados em runtime, utilizando para isto recursos presentes no namespace System.Reflection.Emit.

Conhecendo um pouco de Emit

Outra técnica importante de reflection é o uso das classes do namespace System.Reflection.Emit. Com ele, o programa é capaz de gerar classes e assemblies em tempo de execução, opcionalmente salvando em disco. O conjunto de classes desse namespace foi feito especialmente para ser usado por compiladores e script engines, mas tem grande utilidade em aplicações e bibliotecas comuns, pois por MSIL ser a linguagem mais básica da plataforma .NET, é possível alcançar grande flexibilidade ao escrever o código.

Nota do DevMan

IL é Intermediate Language, também conhecida como MSIL. É a linguagem de mais baixo nível na plataforma .NET. Análoga à linguagem Assembly nas plataformas x86 (não confundir com o conceito de assembly do .NET). As instruções em MSIL são bem mais cruas que em C#, por se tratar de uma linguagem mais próxima da máquina. Mesmo assim, é uma linguagem bem mais simples que Assembly puro, por ter conhecimento de estruturas de alto nível como métodos e objetos.

As principais classes usadas para este propósito são:

• AssemblyBuilder - usada para criar assemblies em runtime;

• DynamicMethod - permite criar um método descartável que pode ser recolhido pelo Garbage Collector;

• ILGenerator - usada para efetivamente emitir código executável;

Dentre essas, uma das classes que merece destaque é a DynamicMethod. Ela foi introduzida no .NET 2.0 e permite que métodos muito leves sejam criados em tempo de execução. Esses métodos podem ser chamados através de delegates com a mesma performance de um método normal. A principal vantagem desses métodos é a possibilidade de serem coletados pelo Garbage Collector.

Analogamente, o .NET Framework 4.0 introduziu o conceito de Collectible Assemblies. Até então, todo assembly carregado não podia ser descarregado até a finalização do AppDomain. Agora, um assembly criado a partir de IL Emitting pode ser marcado como "coletável" e ele poderá ser descartado após o uso. Isso evita que o consumo de memória da sua aplicação cresça indefinidamente ao utilizar bibliotecas que geram assemblies em runtime, como a System.Xml.Serialization.

Attributes: utilizando informações de metadata de forma declarativa

Attributes são um meio declarativo de representar informações de metadados e associá-las, por sua vez, a estruturas de código. Por estruturas de código devem ser levadas em conta construções como propriedades, métodos, classes, eventos, dentre outros tipos de declarações. No entanto, um atributo por si só não deve corresponder a funcionalidade ou comportamento esperado dentro de uma solução. É imprescindível que exista algum meio para interpretar e agir com base nos dados expostos por um objeto deste tipo.

Assim, em qualquer aplicação aonde se pretenda fazer uso de Attributes torna-se necessário, primeiramente, definir um mecanismo para execução dinâmica de um comportamento. Tal mecanismo empregará em runtime técnicas de Reflection, acessando os dados disponibilizados pelos atributos que esperava e disparando ações de acordo com os parâmetros obtidos. Logo, a função de um Attribute acaba consistindo, em termos gerais, no fornecimento de instruções à classe que o analisa sobre como se comportar.

Um atributo é, considerando-se uma aplicação desenvolvida sob o .NET Framework, um tipo herdado direta ou indiretamente da classe System.Attribute. Através de propriedades vinculadas ao mesmo consegue-se, via de regra, estipular quais informações de metadata podem ser vinculadas a uma estrutura de código. Além disso, podem ser classificados em:

• Intrínsecos: disponibilizados como parte do próprio .NET Framework;

• Customizados: criados dentro de uma solução de software a fim de suprir alguma necessidade inerente à mesma.

Por convenção, os nomes de classes que representam atributos são finalizados com o sufixo Attribute. O uso de expressões que envolvam um atributo vinculando o mesmo ao elemento a que se refere dispensa o uso de tal sufixo ao final do nome (isto será demonstrado na aplicação de teste).

A maneira como um Attribute poderá ser empregado nos elementos aos quais estará vinculado é definida por meio do atributo AttributeUsage. Através deste último é possível definir:

• Se o atributo em questão poderá ser utilizado mais de uma vez para um mesmo elemento (propriedade booleana AllowMultiple);

• Para quais tipos de construções de código o atributo que se está considerando poderá ser vinculado. Isto é feito através da seleção de um ou mais valores do enum System.AttributeTargets: Class, Interface, Property, Method, Event, Constructor etc.;

• Se as características de um tipo base serão herdadas por classes que estendem o mesmo (propriedade booleana Inherited, cujo valor default é true).

...
Quer ler esse conteúdo completo? Tenha acesso completo