T4 Templates no .NET
Os T4 templates continuam úteis no .NET moderno para gerar código repetitivo em tempo de build, mas a migração a partir do .NET Framework exige alguns ajustes. Neste artigo, você entende o que mudou no ferramental, quais diretivas precisam de revisão e como automatizar tudo com MSBuild.
Ao longo de anos no ecossistema .NET, a necessidade de gerar código em tempo de build aparece com frequência. Por isso, o T4 segue como uma das ferramentas mais antigas e consolidadas para esse propósito.
O que são T4 templates
O nome T4 significa, segundo algumas fontes, Text Template Transformation Toolkit. Na prática, trata-se de uma linguagem de templates baseada em C# que mistura texto literal com blocos de código executável.
Essa mistura lembra bastante o que fazemos com páginas Razor. Assim, quem já escreveu uma view no ASP.NET Core tende a entender a sintaxe rapidamente.
Um arquivo de T4 templates usa a extensão .tt e intercala texto puro com blocos de código C#. Quando a ferramenta processa o arquivo, o código roda e o texto resultante vai para um arquivo de saída.
Um exemplo simples de T4 templates
Veja um template mínimo, que emite uma linha fixa e depois repete outra linha dentro de um laço:
<#@ template language="C#" #>
This is some plain text that will be emitted verbatim.
<#
// Este código é executado, então não aparece na saída,
// mas muda a forma como o texto seguinte é produzido.
for (int i = 0; i < 5; ++i)
{
#>
This is also emitted. It's in a loop, so we get many copies.
<#
// Outro bloco de código.
}
#>
Se você salvar esse conteúdo em um arquivo SimpleTemplate.tt, basta executar o comando abaixo:
TextTransformCore SimpleTemplate.tt
Em seguida, a ferramenta cria o arquivo SimpleTemplate.txt, com a primeira linha uma vez e a segunda repetida cinco vezes:
This is some plain text that will be emitted verbatim.
This is also emitted. It's in a loop, so we get many copies.
This is also emitted. It's in a loop, so we get many copies.
This is also emitted. It's in a loop, so we get many copies.
This is also emitted. It's in a loop, so we get many copies.
This is also emitted. It's in a loop, so we get many copies.
O T4 é fundamentalmente orientado a texto. Por isso, ele gera C#, F#, VB.NET, HTML, Markdown ou qualquer outro formato textual.
Por que T4 templates ainda importam
Por anos, o T4 ficou amarrado ao .NET Framework. Somente em 2023 a Microsoft lançou uma ferramenta de linha de comando capaz de rodar sobre o .NET 6.0 e, depois, sobre o .NET 8.0.
Segundo o anúncio oficial, o TextTransformCore.exe chegou com o Visual Studio 2022 versão 17.6. Ele fica em {VS_INSTALL_PATH}\Common7\IDE\TextTransformCore.exe e aceita os mesmos argumentos do antigo TextTransform.exe.
No entanto, em fevereiro de 2026 ainda não havia suporte oficial ao .NET 10.0. Além disso, o suporte ao .NET moderno continua mínimo e não vem habilitado por padrão.
A integração do Visual Studio, por sua vez, segue usando a implementação legada baseada em .NET Framework. Ou seja, quem quer recursos atuais da linguagem dentro dos T4 templates precisa trabalhar fora da IDE.
Por que não usar source generators
A pergunta é legítima. Os source generators, que chegaram junto com o .NET 5, oferecem suporte nativo do SDK para gerar código em tempo de build.
Em teoria, eles tornam o T4 desnecessário na maioria dos casos. Na prática, porém, escrever um source generator costuma ser bem mais complexo do que escrever T4 templates.
Os source generators são poderosos, e o Corvus.Json.SourceGenerator é um exemplo notável disso. Contudo, o desenvolvimento deles exige um esforço considerável, com análise de sintaxe, modelo semântico e testes específicos.
Para gerar código repetitivo e direto, o T4 ainda representa uma alternativa viável. Além disso, ele costuma ser muito mais simples de manter.
Projetos reais que usam T4 templates
O Rx.NET e o Reaqtor usam T4 templates de forma extensiva. Por exemplo, eles geram variações de operadores como Min e Max para cada tipo numérico.
Esses projetos também usam T4 para geração de código dirigida por reflexão. Portanto, a necessidade de evoluir essas bases com recursos do .NET moderno motivou a migração descrita a seguir.
O que mudou no ferramental
Para entender a migração, primeiro compare o suporte ao T4 no .NET Framework com o suporte no .NET moderno. As diferenças afetam diretamente a forma como você executa e depura os templates.
Suporte legado aos T4 templates no Visual Studio
Historicamente, para habilitar T4 templates em um projeto do Visual Studio, você adicionava este trecho ao arquivo de projeto:
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
Depois, você informava a associação entre o template e o arquivo gerado. O exemplo abaixo liga Example.tt ao Example.cs:
<ItemGroup>
<None
Update="Example.tt"
Generator="TextTemplatingFileGenerator"
LastGenOutput="Example.cs"
/>
<Compile
Update="Example.cs"
DesignTime="True"
AutoGen="True"
DependentUpon="Example.tt"
/>
</ItemGroup>
Com esse setup, o Visual Studio exibia as opções Run Custom Tool e Debug T4 Template no Solution Explorer. Assim, você executava e depurava os templates sem sair da IDE.
Suporte moderno no .NET
O suporte ao T4 sobre o .NET moderno entrega essencialmente uma única coisa: a ferramenta de linha de comando TextTransformCore. Não existe integração com o Visual Studio.
Os itens de menu continuam na IDE, mas invocam apenas o motor legado baseado em .NET Framework. Dessa forma, não há um jeito suportado de fazer o Visual Studio executar um template com o .NET moderno.
O próprio anúncio da Microsoft reconhece essa limitação. Segundo o texto, ainda não havia suporte a geradores de arquivo na IDE nem a tarefas de MSBuild para .NET 6 ou superior.
O mesmo anúncio aponta um problema conhecido: a transformação falha quando o atributo hostSpecific vale true. Nesse caso, a recomendação é usar hostSpecific="false" sempre que o template não depender de this.Host.
Em outras palavras, migrar significa abrir mão da execução e da depuração integradas. É um trade-off relevante, mas uma boa automação no build compensa boa parte dele.
Ajustes necessários nos T4 templates
Ao levar T4 templates do .NET Framework para o .NET moderno, preste atenção na organização das bibliotecas de classe do runtime. Muitos tipos mudaram de assembly ao longo da evolução da plataforma.
Referências de assembly
No .NET Framework, diretivas como esta eram comuns:
<#@ assembly name="System.Core" #>
No .NET moderno, os tipos foram redistribuídos entre assemblies. Por isso, a diretiva equivalente provavelmente será esta:
<#@ assembly name="System.Linq" #>
Outro erro frequente é o CS1069, que informa que o tipo Stack<> não existe no namespace System.Collections.Generic. Para resolvê-lo, adicione a referência abaixo:
<#@ assembly name="System.Collections" #>
O caso do System.Xml.Linq
Ao usar System.Xml.Linq, você pode encontrar um erro dizendo que o tipo XElement foi encaminhado para System.Private.Xml.Linq. A solução atual é referenciar esse assembly de forma explícita:
<#@ assembly name="System.Private.Xml.Linq" #>
Esse caso incomoda bastante, porque System.Private.Xml.Linq é um detalhe de implementação interna. Portanto, ele pode mudar entre versões do .NET sem aviso.
O problema acontece porque a ferramenta T4 não distingue reference assemblies de runtime assemblies. Ela sempre usa os segundos, enquanto o SDK do .NET pressupõe os primeiros durante a compilação.
Existe um relato desse comportamento no Developer Community do Visual Studio. Assim, vale acompanhar o tíquete antes de confiar nessa referência a longo prazo.
Como automatizar T4 templates com MSBuild
Embora o Visual Studio não ofereça suporte nativo ao T4 moderno, você pode executar os T4 templates como parte do build. Para isso, basta ajustar alguns arquivos de projeto.
A abordagem envolve três elementos. Primeiro, um ItemGroup para os templates; depois, os metadados DependentUpon para o Solution Explorer; por fim, um Target customizado que roda os templates quando necessário.
Se você quiser revisar os fundamentos do motor de build, o artigo sobre as novidades do MSBuild no .NET 11 Preview 1 traz um bom contexto. Além disso, a documentação oficial sobre transformação de texto no processo de build mostra a abordagem clássica baseada em .NET Framework.
Definindo o ItemGroup para os templates
Em um arquivo Directory.Build.props na raiz da solução, defina o seguinte grupo de itens:
<ItemGroup>
<TextTemplates Include="**\*.tt">
<GeneratedOutput>%(Filename).cs</GeneratedOutput>
<GeneratedOutputRelativePath>%(RelativeDir)%(GeneratedOutput)</GeneratedOutputRelativePath>
</TextTemplates>
</ItemGroup>
O glob **\*.tt inclui no grupo todos os arquivos .tt de qualquer projeto da solução. Com isso, nenhum template fica de fora por esquecimento.
Cada item recebe dois metadados. O GeneratedOutput guarda o nome do arquivo de saída, enquanto o GeneratedOutputRelativePath guarda o caminho relativo ao diretório do projeto.
Lidando com convenções de nome diferentes
Alguns projetos misturam convenções, gerando File.cs em um lugar e File.generated.cs em outro. Nesses casos, alguns templates trazem a diretiva abaixo:
<#@ output extension=".generated.cs" #>
O Reaqtor resolve isso com uma variação mais robusta, que verifica qual arquivo existe em disco para inferir a convenção:
<ItemGroup>
<TextTemplates Include="**\*.tt">
<GeneratedOutput Condition="Exists('%(RootDir)%(Directory)%(Filename).generated.cs')">%(Filename).generated.cs</GeneratedOutput>
<GeneratedOutput Condition="%(GeneratedOutput) == ''">%(Filename).cs</GeneratedOutput>
<GeneratedOutputRelativePath>%(RelativeDir)%(GeneratedOutput)</GeneratedOutputRelativePath>
</TextTemplates>
</ItemGroup>
Assim, se o arquivo .generated.cs já existir, o MSBuild usa esse nome. Caso contrário, ele assume o padrão .cs.
Configurando o DependentUpon para o Solution Explorer
Para que os arquivos gerados apareçam aninhados sob seus T4 templates no Solution Explorer, adicione este bloco ao Directory.Build.targets:
<ItemGroup>
<Compile Update="@(TextTemplates->'%(GeneratedOutputRelativePath)')">
<DesignTime>true</DesignTime>
<AutoGen>true</AutoGen>
<DependentUpon>%(TextTemplates.Filename).tt</DependentUpon>
</Compile>
</ItemGroup>
Esse bloco precisa ficar no .targets, e não no .props. Isso porque o grupo de itens Compile ainda não existe quando o MSBuild processa os arquivos .props, já que o SDK o preenche nas etapas seguintes.
Com essa configuração, você pode remover dos projetos individuais todas as entradas manuais que associavam template e saída. A partir daí, a solução gerencia essa associação de forma centralizada.
Definindo o Target de execução
Ainda no Directory.Build.targets, defina o target responsável por executar os templates:
<Target
Name="_TransformTextTemplates"
BeforeTargets="PreBuildEvent"
Condition="@(TextTemplates) != '' and $(DevEnvDir) != ''"
Inputs="@(TextTemplates)"
Outputs="@(TextTemplates->'%(GeneratedOutputRelativePath)')">
<Exec
WorkingDirectory="$(ProjectDir)"
Command='"$(DevEnvDir)TextTransformCore.exe" "%(TextTemplates.Identity)"' />
</Target>
O atributo BeforeTargets="PreBuildEvent" faz o target rodar antes da compilação. Dessa forma, o compilador sempre enxerga a versão mais recente do código gerado.
Detalhes importantes do target de T4 templates
O target parece simples, mas três decisões de design merecem atenção. Cada uma resolve um problema prático de quem mantém T4 templates em equipe.
Execução condicional ao Visual Studio
A condição $(DevEnvDir) != '' garante que o target só rode onde o Visual Studio está presente. Isso inclui builds dentro da IDE e builds em um Developer Command Prompt.
Essa condição é necessária porque o TextTransformCore não faz parte do .NET SDK. Na verdade, a Microsoft distribui a ferramenta junto com o Visual Studio.
Versionando os arquivos gerados
Os arquivos gerados ficam no controle de versão. Assim, quem clonar o repositório sem o Visual Studio instalado ainda compila o projeto normalmente.
A ferramenta T4 só precisa rodar quando alguém modifica um template. Além disso, apenas quem tem o Visual Studio disponível precisa executá-la.
Build incremental por timestamps
Os atributos Inputs e Outputs informam ao MSBuild a relação entre as entradas e as saídas do target. Com isso, o MSBuild só executa um template quando o .tt é mais recente que o arquivo gerado correspondente.
Esse comportamento de build incremental evita reprocessamento desnecessário. Em soluções com dezenas de T4 templates, a diferença no tempo de build fica bem perceptível.
Vale notar que essa comparação considera apenas o template e a saída. A tarefa clássica do MSBuild para T4, documentada pela Microsoft, também observa arquivos incluídos, mas ela depende do motor baseado em .NET Framework.
Vale a pena migrar T4 templates
A migração de T4 templates para o .NET moderno é uma escolha deliberada. Você abre mão da integração nativa com o Visual Studio, incluindo a depuração dos templates.
Em troca, você ganha recursos do .NET atual dentro dos templates e uma execução automatizada e incremental no build. Para projetos que precisam evoluir com o ecossistema, esse trade-off costuma valer a pena.
As mudanças nos arquivos de projeto são relativamente simples, desde que você entenda os mecanismos do MSBuild envolvidos. O resultado é uma pipeline de geração de código integrada, confiável e transparente.
Se o seu objetivo for criar modelos de projeto, e não gerar código, o caminho é outro. Nesse caso, veja como criar templates no Visual Studio com o mecanismo próprio da IDE.
Conclusão
Os T4 templates seguem relevantes para quem precisa gerar código repetitivo sem a complexidade de um source generator. Contudo, levar esses templates para o .NET moderno exige atenção a alguns pontos.
Primeiro, o TextTransformCore é o único entregável do suporte ao T4 moderno, sem integração com o Visual Studio. Em seguida, as diretivas de assembly precisam de revisão, porque os tipos mudaram de lugar na BCL.
Além disso, Directory.Build.props e Directory.Build.targets centralizam toda a configuração da solução. Por fim, o build incremental e o versionamento dos arquivos gerados mantêm o projeto compilável mesmo sem a IDE.
Para equipes que dependem de T4 templates e querem usar generic math ou APIs novas da BCL, essa migração é um caminho viável e recomendado. Portanto, comece pelos templates mais simples e avance conforme os erros de assembly aparecerem.
FAQ
O T4 funciona no .NET 10?
Em fevereiro de 2026, não havia suporte oficial ao .NET 10. A ferramenta TextTransformCore rodava sobre o .NET 6 e, depois, sobre o .NET 8.
Onde fica o TextTransformCore?
Ele acompanha o Visual Studio 2022 a partir da versão 17.6. O executável fica na pasta Common7\IDE da instalação do Visual Studio.
Consigo depurar templates T4 do .NET moderno no Visual Studio?
Não. Os comandos Run Custom Tool e Debug T4 Template continuam usando o motor legado baseado em .NET Framework.
Por que referenciar System.Private.Xml.Linq?
Porque a ferramenta T4 carrega runtime assemblies, e não reference assemblies. Assim, o tipo XElement aparece encaminhado para esse assembly interno.
Preciso do Visual Studio no servidor de build?
Não, desde que os arquivos gerados estejam versionados. O target só roda quando a variável DevEnvDir existe, então o build segue normal sem a IDE.
T4 ou source generators?
Source generators são nativos do SDK e mais poderosos. No entanto, para geração simples e repetitiva, o T4 costuma ser mais fácil de escrever e manter.