Este artigo foi traduzido por máquina.

Modelos do T4

Reduzindo os obstáculos de geração de código com o T4

Peter Vogel

 

O Microsoft.NET Framework faz uso extensivo de geração de código em tempo de design (quando arrastar um controle para uma superfície de design gera código) e em tempo de execução (quando LINQ gera as instruções de SQL que recuperam dados). Geração de código, obviamente, torna os desenvolvedores mais produtivo, reduzindo a quantidade de código que um desenvolvedor tem que escrever, mas pode ser especialmente útil quando mais ou menos idêntico código é usado em muitas soluções. Como implementar esse código semelhante (mas não idêntico) em um novo aplicativo, é muito fácil de introduzir novos erros.

Mesmo que os desenvolvedores podem tirar proveito de todas as ferramentas de geração de código o.NET Framework usa na criação de aplicativos, muito poucos fazem uso extensivo de geração de código em sua prática de desenvolvimento diário. Há um número de razões para isso: elas se preocupe que a incorporação de geração de código requer um novo conjunto de ferramentas não têm o tempo para aprender; eles não têm experiência em reconhecer problemas que resolverá a geração de código e em projetar soluções que integram o código gerado com o código "manuscrito"; e eles entendem que algumas soluções de geração de código podem exigir mais tempo de manutenção (ou mesmo de usar) não serão salvas, aplicando a solução.

A Microsoft Text Template Transformation Toolkit (T4) aborda muitas dessas questões, proporcionando uma maneira simple de implementar soluções de geração de código que utilizam ferramentas e técnicas os desenvolvedores já estão confortáveis com. T4 faz isso por reconhecer que uma solução típica envolve dois tipos de código: código clichê que não muda de geração de um código para outro e códigos dinâmicos que mudam. T4 simplifica a geração de código, permitindo que os desenvolvedores simplesmente digite a parte de uma solução de código clichê em um arquivo. Em T4, o código dinâmico (normalmente uma pequena parte de uma solução de geração de código) é gerado usando um conjunto de marcas que são muito semelhantes às marcas de um ASP.NET MVC desenvolvedor iria usar na criação de uma exibição, ou que um ASP.Desenvolvedor NET usaria para incorporar o código do lado do servidor em um arquivo. aspx.

Usando uma solução baseada em T4 aproveita as habilidades que você já tem, permitindo que você especifique as entradas para a geração de código em qualquer linguagem de programação que você já está usando. T4 não gera soluções de código neutro (o código gerado por uma solução de T4 é sempre em alguma linguagem de programação específica), mas a maioria dos desenvolvedores não precisam de soluções de código neutro.

Definir soluções de geração de código

Enquanto T4 torna mais fácil para criar soluções de geração de código, ele não resolver os problemas que os desenvolvedores têm em reconhecer quando uma solução de geração de código seria útil e realmente projetar uma solução. Problemas de geração de código resolve geralmente compartilham três características:

  1. O código gerado vai para um arquivo separado da devel­código da oper. Isso garante que o processo de geração de código não irá interferir com o código do desenvolvedor. Normalmente, isso significa que o código gerado vai para uma nova classe que o desenvolvedor usará a partir de seu código "manuscrito". Pode fazer sentido para gerar uma classe parcial que o desenvolvedor pode não só chamar mas estender — mas código do desenvolvedor e o código gerado ainda são mantidos em arquivos separados.
  2. O código gerado é repetitivo: O código da solução é um modelo que pode ser repetido muitas vezes, muitas vezes com pequenas variações. Isso garante que a geração de código é mais simples e mais fáceis de manter do que o equivalente código escrito à mão.
  3. Em comparação com a solução manuscrita, a solução gerada requer muito menos insumos (idealmente, nenhuma entrada em tudo — a solução de geração de código determina o que precisa ser feito no ambiente). Se o número de entradas é grande ou difícil de determinar, os desenvolvedores podem considerar a solução manuscrita como mais simples do que a solução gerada.

Tendo em conta estas características, são três tipos de cenários vale a pena investigar para geração de código. Os desenvolvedores tendem a se concentrar sobre o primeiro cenário ("cenário final"), em que apenas algumas entradas são usadas para gerar uma grande quantidade de código que é usado com freqüência (pense o Entity Framework, por exemplo). Na verdade, o código mais frutíferas -­oportunidades de geração cair em dois outros cenários.

O segundo cenário é a mais óbvia: quando uma grande quantidade de código precisa ser gerado. Nesse caso, é claramente benéfico evitar escrever várias linhas de código repetitivo. Enquanto a maioria das soluções de geração de código são usados em várias aplicações, soluções nesta categoria podem ser vale a pena mesmo se usado em apenas uma única aplicação. Em vez de código de gravação generalizada com muitos If instruções para lidar com cada situação — cada se dobrando a complexidade lógica do código de instrução — uma solução de geração de código pode gerar o código específico exigido para cada conjunto de condições. Em vez de mão muitas classes que compartilham de uma interface de codificação, geração de código pode ser usada para criar as classes individuais (supondo que as classes compartilham uma estrutura comum).

Mas o terceiro tipo de cenário é o mais comum: quando algumas entradas irão gerar um pouco de código para ser usado em muitas aplicações. Nesse cenário, a quantidade de código repetido em qualquer aplicativo em particular é pequena, mas a atividade suporta o código é tão comum que a solução acaba gerando uma grande quantidade de código — não apenas em qualquer um aplicativo.

Por exemplo, eis alguns típicos que ADO do código.NET desenvolvedores escrevem o tempo todo:

string conString =
  System.Configuration.ConfigurationManager.
ConnectionStrings["Northwind"].ConnectionString;

Embora isto seja uma quantidade trivial de código, ele é o código que é repetido — com apenas o nome da mudança de Cadeia de caracteres de conexão — no aplicativo após a aplicação. Além disso, na ausência de qualquer suporte a IntelliSense para o nome de Cadeia de caracteres de conexão, o código é sujeito a erro: é aberta a erros "contagens de ortografia" que serão descobertos somente em tempo de execução (provavelmente quando alguém que tem entrada para sua apreciação é olhando por cima do seu ombro). Outro exemplo é implementar INotifyPropertyChanged, deixando o colaborador aberto para "contagens de ortografia" erros em cada propriedade.

Esse código para recuperar uma Cadeia de caracteres de conexão chamada Northwind seria mais útil se existia alguma solução de geração de código para criar uma classe ConnectionManager para cada Cadeia de caracteres de conexão, como este:

string conString = ConnectionManager.Northwind;

Uma vez que você reconhece uma oportunidade para uma solução de geração de código, o próximo passo é escrever para fora uma amostra do código que gera a solução. Neste caso, a classe ConnectionManager pode parecer como este:

public partial class ConnectManager
{
  public static string Northwind
    {
      get
        {
          return System.Configuration.ConfigurationManager.
ConnectionStrings["Northwind"].ConnectionString;
        }
    }
}

Este código corresponde aos critérios para uma solução de geração de código: é repetitivo (o código de propriedade é repetida para cada Cadeia de caracteres de conexão) com apenas pequenas mudanças (o nome da Cadeia de caracteres de conexão) e o nome da propriedade e o número de entradas é pequena (apenas os nomes das seqüências de caracteres de conexão).

Seu primeiro modelo de geração de código

Uma solução de T4 pode consistir em um "pacote de geração de código": um arquivo onde o desenvolvedor insere as entradas para o processo de geração de código e um arquivo de modelo que gera código dessas entradas. Ambos os arquivos são arquivos de modelo T4 e são criados usando as mesmas ferramentas de programação que você usar para escrever seus aplicativos. Esse design permite que você separe seu modelo de geração de código do arquivo que o desenvolvedor usa para fornecer entradas para o processo.

Para começar a criar sua solução de geração de código, você deve adicionar o arquivo de modelo T4 que irá gerar o seu código para um aplicativo onde você pode testá-lo — de preferência, um aplicativo semelhante para os que você espera que sua solução para ser usado no. Para adicionar o arquivo de modelo T4, na caixa de diálogo Add New Item no Visual Studio, mostrado na Figura 1, adicionar um modelo de texto especificando um nome apropriado para o seu modelo de geração de código (por exemplo, ConnectionManagerGenerator). Se sua versão do Visual Studio não tem a opção de modelo de texto, adicione um novo arquivo de texto (também encontrado na seção geral), dando o arquivo de extensão "TT" tratamento de gatilho T4. Se você adicionar um arquivo de texto que você obterá uma mensagem de aviso que você pode ignorar com segurança.

Adding a T4 Template
Figura 1 Adicionando um modelo de T4

Se você examinar as propriedades para o novo arquivo de modelo, você vai encontrar que sua ferramenta de personalizado propriedade tiver sido definida para TextTemplatingFileGenerator. Esta ferramenta personalizada é executada automaticamente pelo Visual Studio e é o host que gerencia o processo de geração de código. Em T4, o conteúdo do arquivo de modelo é passado para o host de geração de código, que coloca o código gerado resultante no arquivo de filho aninhado do arquivo de modelo.

Se você adicionou um arquivo de texto ao seu projeto, seu arquivo de modelo estará vazio; Se você fosse capaz de adicionar um arquivo de modelo, ele conterá duas directivas de T4, marcadas com < # @... # > delimitadores (se você tiver adicionado um arquivo de texto, você precisará adicionar estas directivas). Estas directivas especificam o idioma que o modelo será escrito (não o idioma para o código gerado) e a extensão para o arquivo filho. Neste exemplo, as duas directivas definir a linguagem de programação para o modelo de Visual Basic e a extensão de arquivo para o arquivo filho que contém o código gerado para. generated.cs:

<#@ template language="VB" #>
<#@ output extension=".generated.cs" #>

Para criar o aplicativo tradicional "Hello, World", basta adicione o código para o arquivo de modelo (Observe que enquanto o idioma em que do modelo está sendo escrito Visual Basic, o modelo está gerando o código c#):

public class HelloWorld
{
  public static string HelloWorld(string value)
  {
    return "Hello, " + value;
  }
}

Este exemplo usa somente código clichê. No T4, código clichê é copiado diretamente do modelo para o arquivo de código. No Visual Studio 2010, que deve acontecer quando você alternar longe do arquivo de modelo ou salvá-lo. Você também pode acionar a geração de código clicando sobre o arquivo de modelo no Solution Explorer e selecionando executar Personalizar ferramenta no seu menu de contexto ou clicando no botão transformar todos os modelos na parte superior do Gerenciador de soluções.

Após disparo geração, se você abrir o arquivo de código do modelo (que agora terá a extensão especificada na Directiva de saída do modelo), você encontrará que ele contém o código especificado em seu modelo. Visual Studio também terá feito uma compilação de plano de fundo do seu novo código, onde você vai encontrar que você pode usar o código gerado do resto do seu aplicativo.

Gerando código

Código clichê não é suficiente, no entanto. A solução de ConnectionManager deve gerar dinamicamente uma propriedade para cada Cadeia de caracteres de conexão que do aplicativo requer. Para gerar esse código, você deve adicionar código de controle para gerenciar o processo de geração de código e algumas variáveis que armazenará as entradas do desenvolvedor usando sua solução de geração de código.

O ConnectionManager usa um ArrayList (que eu tenho chamado conexões) do namespace System. Collections para manter a lista de seqüências de caracteres de conexão que formam a entrada para o processo de geração de código. Para importar o namespace usado pelo código dentro de seu modelo, você usar a diretiva de T4 de importação:

<#@ Import Namespace="System.Collections" #>

Agora você pode adicionar qualquer código estático que começa sua classe gerada. Porque eu estou gerando código c#, o código inicial para o ConnectionManager parece com isto:

public partial class ConnectionManager
{

Eu agora deve adicionar o código de controle que dinamicamente irá gerar o código de saída. Código que controla a geração (código que está a ser executado, em vez de copiados para o arquivo filho) deve estar entre o < #... # > delimitadores. Neste exemplo, para tornar mais fácil a distinção entre o código de controle e o código que está sendo gerado, eu escrevi o código de controle no Visual Basic (isso não é um requisito do processo de geração de código). O código de controle para a solução de ConnectionManager faz um loop através da coleção de conexões para cada Cadeia de caracteres de conexão:

<#
  For Each conName As String in Connections
#>

Para complementar qualquer código de controle no seu modelo, você também precisará incluir quaisquer expressões cujos valores são a ser incorporada no seu código gerado dinamicamente. Na solução de ConnectionManager, o nome da Cadeia de caracteres de conexão tem que ser incorporada na declaração da propriedade e para o parâmetro passado para a coleção ConnectionStrings. Para avaliar uma expressão e ter seu valor inserido no código clichê, a expressão deve ser entre o < # = … # > delimitadores. Este exemplo insere dinamicamente o valor da variável conName em dois lugares no código estático dentro os para cada loop:

public static string <#= conName #>
{
  get
  {
    return System.Configuration.ConfigurationManager.
ConnectionStrings["<#= conName #>"].ConnectionString;
  }
}
<#
  Next
#>
}

Tudo o que resta é para definir o ArrayList que conterá a lista de conexão nomes de Cadeia de caracteres. Para isso, vou usar um recurso de classe de T4. Recursos da classe T4 geralmente são usados para definir funções auxiliares, mas também podem ser usados para definir campos ou quaisquer outros itens de nível de classe que serão usados durante o processo de geração de código. Recursos de classe devem aparecer no final de um modelo, tal como este:

<#+
  Dim Connections As New ArrayList()
#>

Este modelo T4 constitui a primeira parte da solução ConnectionManager — o modelo de geração de código. Agora você precisa criar a segunda parte da solução: O arquivo de entrada que o colaborador irá utilizar para fornecer entradas para o processo de geração de código.

Usando o pacote de geração de código

Para fornecer um lugar para o desenvolvedor digitar as entradas para o processo de geração de código, você adiciona um segundo modelo T4 para o aplicativo em que você está testando sua solução de geração de código. Este modelo deve ter uma incluir diretriz que copia o modelo de geração de código para este modelo. Porque eu chamado meu arquivo de modelo de geração de código ConnectionManagerGenerator, o arquivo de modelo de entrada para a solução de ConnectionManager parece com isto:

<#@ template language="VB" #>
<#@ output extension=".generated.cs" #>
<#@ Import Namespace="System.Collections" #>
<#
#>
<#@ Include file="ConnectionManagerGenerator.tt" #>

Quando a geração de código é executada, o processo de host realmente monta um intermediário.NET programa das directivas, código de controle e estática de código especificado em seus modelos e, em seguida, executa o programa resultante. É a saída desse programa intermediário que é derramado no arquivo de filho do modelo. O resultado usando a diretiva Include é mesclar seu modelo de geração de código (com sua declaração da ArrayList conexões) com o conteúdo desse arquivo para criar esse programa intermediário. Tudo o que o desenvolvedor usando sua solução tem que fazer é adicionar o código para este modelo que irá definir as variáveis usadas por seu modelo de geração de código. Esse processo permite que os desenvolvedores especificar as entradas para a geração de código usando a linguagem de programação, para que eles são usados.

Para a solução de ConnectionManager, o desenvolvedor precisa adicionar o nome de seqüências de caracteres de conexão listados no app. config do aplicativo ou no arquivo Web. config para o ArrayList de conexões. Porque essas configurações são parte do código de controle que tem de ser executado, esse código deve ser colocado dentro do < #... # > delimitadores. Código do desenvolvedor também deve preceder a incluir diretriz para que as variáveis estão definidas antes que seu código seja executado.

Para gerar um ConnectionManager para duas seqüências de conexão chamado Northwind e PHVIS, o desenvolvedor teria Adicione este código para o modelo de entrada antes da incluir diretriz:

<#
  Me.connections.Add("Northwind")
  Me.connections.Add("PHVIS")
#>
<#@ Include file="ConnectionManagerGenerator.tt" #>

Agora você tem um pacote de geração de código que consiste no arquivo de modelo de geração de código e o arquivo de modelo de entrada. Os desenvolvedores usando sua solução devem copiar ambos os arquivos em seu aplicativo, defina as variáveis no arquivo de entrada e fechar ou salvar o arquivo de entrada para gerar o código de solução. Um desenvolvedor de geração de código empreendedora poderia configurar o pacote de geração de código como um modelo de item de Visual Studio que inclui ambos os arquivos de modelo. Enquanto não apropriado para a solução de ConnectionManager, se um desenvolvedor precisa para gerar um outro conjunto de código com base em entradas diferentes, ele só precisa fazer uma segunda cópia do arquivo de entrada para segurar o segundo conjunto de factores de produção.

Não há uma ruga na estrutura da solução: qualquer aplicativo que usa esta solução terá o modelo de entrada e o modelo de geração de código. Na solução de ConnectionManager, se ambos modelos geram código que Visual Studio compila, os dois arquivos de código resultante serão ambos definem uma classe chamada Gerenciador de conexões e o aplicativo não vai compilar. Existem várias maneiras de evitar isso, mas é a maneira mais simples de alterar seu modelo de geração de código para que seu arquivo de código gerado tem uma extensão que Visual Studio não irá reconhecer. Alterar a Directiva de saída no arquivo de modelo de geração de código faz o truque:

<#@ output extension=".ttinclude" #>

Recursos e ferramentas de geração de código

Além de páginas da biblioteca MSDN, sua melhor fonte para obter informações sobre o uso de T4 é blog de Oleg Sych em olegsych.com— eu vim certamente depender de seu idéias (e ferramentas) no desenvolvimento de minhas próprias soluções de geração de código. Sua caixa de ferramentas de T4 inclui vários modelos para desenvolver soluções de T4 (incluindo um modelo para gerar Múltiplo arquivos de saída de um único modelo de T4 e outras ferramentas para gerenciar o processo de geração de código). Kit de ferramentas do sych também inclui pacotes para vários cenários de geração de código.

Visual Studio trata essencialmente T4 templates como arquivos de texto — que significa que você não obter suporte IntelliSense ou realce ou, realmente, qualquer coisa que os desenvolvedores esperam de um editor. No Gerenciador de extensão do Visual Studio, você encontrará várias ferramentas que irão reforçar o seu desenvolvimento de T4 de experiência. Ambos Visual T4 de Clarius Consulting (bit.ly/maZFLm) e T4 Editor de Devart (bit.ly/wEVEVa) vai lhe dar muitas das características que você toma para concedido em um editor. Como alternativa, você pode obter o Editor de T4 (ambos gratuito ou PRO edição) de tangível engenharia (ver Figura 2) em bit.ly/16jvGY, que inclui um designer visual, você pode usar para criar pacotes de geração de código da Unified Modeling Language (UML)-gosta de diagramas.

The Default Editor for T4 Template Files (Left) Isn’t Much Better than NotePad—Adding the Tangible Editor (Right) Gives You the Kind of Features You Expect in Visual Studio
Figura 2 O Editor padrão para arquivos de modelo T4 (à esquerda) não é muito melhor do que o bloco de notas — adicionar o Editor tangível (à direita) dá-lhe o tipo de recursos que você espera no Visual Studio

Como com qualquer outra solução baseada em código, é improvável que sua solução irá funcionar na primeira vez. Erros de compilação em seu Controlarar modelo código são relatados na lista de erros depois que você Selecionar ferramenta personalizado para executar menu de um arquivo de modelo de contexto. No entanto, mesmo se seu código de Controlarar é compilado, você pode achar que arquivo de filho do modelo está vazio, exceto para a palavra ErrorGeneratingOutput. Isso indica que o código de Controlarar em seu pacote de geração de código está gerando um erro quando ele é executado. A menos que seu erro é óbvio, você vai precisar depurar esse código de Controlarar.

Para depurar seu pacote de geração, primeiro você deve definir o atributo debug sobre a Directiva de modelo para True, como este:

<#@ template language="VB" debug="True"#>

Agora você pode definir um ponto de interrupção em seu código de controle e tiver o Visual Studio respeitá-lo. Em seguida, a maneira mais confiável para depurar seu aplicativo é iniciar uma segunda versão do Visual Studio e, no menu Debug, selecione Attach to Process. Na caixa de diálogo resultante, você selecionar a outra cópia em execução do devenv. exe e clique no botão Anexar. Agora você pode voltar para sua original cópia do Visual Studio e usar Executar ferramenta Personalizar menu de contexto do arquivo de entrada para iniciar a execução do seu código.

Se esse processo não funcionar, você pode acionar a depuração, inserindo esta linha de código de controle do seu modelo:

System.Diagnostics.Debugger.Break()

Com o Visual Studio 2010 no Windows 7, você deve adicionar esta linha antes de sua chamada para o método de interrupção:

System.Diagnostics.Debugger.Launch()

Quando você executar seu código de modelo, esta linha exibe uma caixa diálogo que permite que você reinicie o Visual Studio ou depurá-lo. Selecionando a opção debug iniciará uma segunda cópia do Visual Studio já anexado ao processo que executa seu código de modelo. Sua instância inicial do Visual Studio será desabilitada enquanto você está Depurando seu código. Infelizmente, quando a sessão de depuração termina, Visual Studio vai ficar nesse modo desabilitado. Para evitar isso, você precisará alterar uma das configurações no registro do Windows a Visual Studio (veja post do Sych sobre depuração de T4 no bit.ly/aXJwPx para detalhes). Você também precisará de Lembre-se de excluir esta declaração uma vez que você tenha fixado seu problema.

Naturalmente, esta solução ainda conta com o desenvolvedor entrar corretamente os nomes das seqüências de caracteres de conexão no arquivo de entrada. Uma solução melhor teria ConnectionManager incluir o código que lê as seqüências de conexão do arquivo de configuração do aplicativo, eliminando a necessidade para o desenvolvedor digitar quaisquer entradas. Infelizmente, porque o código está sendo gerado em tempo de design em vez de em tempo de execução, você não pode usar o ConfigurationManager para ler o arquivo de configuração e terá que usar as classes System. XML para processar o arquivo config. Você também precisará adicionar um Diretiva de assembly para pegar essas classes, como fiz anteriormente para obter ArrayList de System. Collections. Você também pode adicionar referências a suas próprias bibliotecas personalizadas por configuração o assembly atributo de nome da directiva para o caminho completo para a DLL:

<#@ assembly name="C:\PHVIS\GenerationUtilities.dll" #>

Estes são os fundamentos para a adição de geração de código para seu kit de ferramentas e aumentar sua produtividade — juntamente com a qualidade e a confiabilidade do seu código. T4 torna mais fácil para você começar, permitindo que você criar soluções de geração de código usando um conjunto de ferramentas familiar. O maior problema que você vai enfrentar no uso de T4 é aprender a reconhecer oportunidades de aplicação dessas ferramentas.

Peter Vogel* é principal em PH & V Information Services. Seu último livro foi "geração de código prático em.NET"(Addison-Wesley Professional, 2010). PH & serviços de informações da v especializa-se em facilitar a concepção de arquiteturas baseadas no serviço e na integração.Tecnologias NET para essas arquiteturas. Além de sua prática de consultoria, Vogel escreveu curso de design da aprendizagem árvore internacional SOA, que é ensinado em todo o mundo.*

Graças ao seguinte especialista técnico para revisão deste artigo: Gareth Jones