CodeGym /Cursos /C# SELF /Introdução a Source Genera...

Introdução a Source Generators

C# SELF
Nível 63 , Lição 3
Disponível

1. Introdução

Quando você escreve código, provavelmente às vezes já pensou: «Por que eu copio o mesmo padrão de código para várias classes?» Ou: «Por que serialização, logging, mapeamento de dados — tudo isso são tantas linhas repetitivas?» Às vezes dá vontade que alguém (ou alguma coisa) escreva esse template trabalhoso por você.

É aí que entram os Source Generators — uma funcionalidade nova no C#, aparecida no .NET 5 e que continua evoluindo. Um Source Generator é uma biblioteca que roda em tempo de compilação e pode gerar dinamicamente código C# que é automaticamente incluído no seu projeto antes da build final.

Por que isso é útil?

  • Automatiza a rotina: Evita escrever classes/métodos repetitivos (boilerplate).
  • Segurança checada pelo compilador: O código gerado é compilado junto com o seu (diferente de T4 ou reflexão).
  • Alta performance: Serialização, DI, mapeamento etc. sem custo de reflection em runtime.
  • Suporte a padrões modernos: Implementa abordagens que seriam difíceis/caras sem geração de código.

Como os Source Generators funcionam "por baixo do capô"?

Source Generator é uma biblioteca .NET (normalmente um projeto do tipo Class Library) que implementa a interface ISourceGenerator. Durante a compilação o Roslyn executa todos os geradores conectados, dando a eles acesso à árvore de sintaxe do seu código.

O gerador analisa seu código, decide o que e onde gerar, e cria novos arquivos C# que o compilador logo compila.

Geração automática de ToString

Vamos começar com algo simples. Imagine que temos uma classe com muitas propriedades e precisamos implementar o ToString. Escrever à mão fica assim:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString()
        => $"Person(Name={Name}, Age={Age})";
}

Mas se as propriedades ficam muitas — dá preguiça e dá para esquecer de atualizar algo. O Source Generator pode fazer isso por você!

2. Como criar seu Source Generator?

Criando o projeto

Abra o JetBrains Rider ou o Visual Studio, crie um novo projeto do tipo Class Library (.NET Standard) — são esses projetos que podem ser geradores. Depois adicione os pacotes NuGet:

  • Microsoft.CodeAnalysis.CSharp
  • Microsoft.CodeAnalysis.Analyzers

Atributos importantes

  • [Generator] — indica que essa classe é um Source Generator.

Template mínimo do gerador

Aqui vai um exemplo mínimo funcional:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Dá para registrar ações adicionais (opcional)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var code = @"
namespace Generated
{
    public static class HelloWorld
    {
        public static string SayHello() => ""Privet, mir! Ya sgenerirovan!"";
    }
}";
        context.AddSource("HelloWorldGenerator", SourceText.From(code, Encoding.UTF8));
    }
}

Esse gerador simples sempre adiciona a classe estática HelloWorld com o método SayHello durante a compilação.

Como usar Source Generators no app principal?

Referencie o projeto-gerador como pacote NuGet ou Project Reference na seção Analyzer (veja — documentação oficial).

O código gerado já fica disponível no seu projeto — não precisa conectar nada extra, só use:

// Isso será gerado automaticamente!
using Generated;

Console.WriteLine(HelloWorld.SayHello());

3. Exemplo real: geração automática de ToString

Vamos supor que queremos que todas as classes marcadas com o atributo [AutoToString] recebam automaticamente a implementação do método ToString. Para isso precisamos:

  • Criar um atributo.
  • Analisar todas as classes com esse atributo.
  • Para cada classe gerada, criar o método ToString.

Atributo

[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute
{
}

Uso no código

[AutoToString]
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

Logica simples de geração

O gerador vai procurar classes com [AutoToString] e gerar algo como:

public override string ToString() 
    => $"Product(Name={Name}, Price={Price})";

Trecho real do código do gerador

A ideia básica — percorrer a árvore de sintaxe com Roslyn:

public void Execute(GeneratorExecutionContext context)
{
    // Analisamos todas as árvores de sintaxe
    foreach (var tree in context.Compilation.SyntaxTrees)
    {
        var root = tree.GetRoot();
        // Procuramos todas as classes com o atributo desejado (exemplo simples!)
        var classes = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Where(c => c.AttributeLists
                         .SelectMany(al => al.Attributes)
                         .Any(a => a.Name.ToString().Contains("AutoToString")));

        foreach (var @class in classes)
        {
            var className = @class.Identifier.Text;
            // Pegamos todas as propriedades da classe
            var props = @class.Members
                .OfType<PropertyDeclarationSyntax>()
                .Select(p => p.Identifier.Text)
                .ToArray();

            var toStringCode = string.Join(", ", props.Select(p => $"{p}={{this.{p}}}"));
            var generated = $@"
partial class {className}
{{
    public override string ToString() => $""{className}({toStringCode})"";
}}";

            context.AddSource($"{className}_ToString", SourceText.From(generated, Encoding.UTF8));
        }
    }
}

Repare: para código de produção é melhor fazer uma análise mais correta usando o SemanticModel do Roslyn.

4. Dicas úteis

O que prestar atenção

Source Generators não conseguem modificar código-fonte existente — apenas criar novos arquivos (por exemplo, classes/métodos partial adicionais). Isso significa que se sua classe for declarada como partial, você pode gerar métodos ou propriedades adicionais para ela.

Às vezes é complicado interpretar corretamente a sintaxe e cobrir todos os cenários da linguagem (classes aninhadas, generics, modificadores etc.). O autor do gerador precisa garantir que o código gerado seja compilável e não quebre o projeto.

Outra armadilha — se você gera métodos que implementam uma interface, certifique-se de que os arquivos são gerados a cada build. Caso contrário podem surgir erros estranhos de compilação. Ferramentas modernas costumam lidar com isso, mas é bom ficar atento.

Source Generators vs. reflection

Reflection: roda em runtime, custoso em recursos, não é verificado pelo compilador, e costuma ser lento em grandes volumes de dados.

Source Generator: gera código em tempo de compilação. Tudo é checado estaticamente, a IDE vê os métodos, autocomplete funciona, e a performance é como a de código C# normal.

Benefícios práticos

  • System.Text.Json: geração de serialização/deserialização sem reflection.
  • Projeto de containers DI: por exemplo, Microsoft.Extensions.DependencyInjection com geração do grafo de dependências.
  • Mapeadores como Mapster: troca de reflection por geração de código de mapping em compile-time.
  • Frameworks de teste: geração automática de métodos de teste baseada em atributos.
  • ASP.NET Minimal APIs (a partir do .NET 7): geração de endpoint handlers.

Configuração, parâmetros e opções

Geradores podem ser configurados via parâmetros do MSBuild, arquivos adicionais e convenções. Dá para, por exemplo, gerar diferentes ToString dependendo do ambiente (Debug/Release) ou da configuração da aplicação.

Como os Source Generators se relacionam com tarefas reais

Para um dev que busca código limpo e rápido, é uma ferramenta excelente: menos rotina, mais checagens em compile-time, sugestões da IDE e refatoração segura. Conhecimento sobre geradores tem aparecido mais em entrevistas — desde serialização e DI até mapeamento.

Ciclo de vida de um Source Generator

Fase O que acontece
1. Projeto referenciado Seu gerador é adicionado como analyzer/reference
2. Roslyn compila o código O gerador recebe o AST (árvore sintática abstrata)
3. Gerador executa Adiciona novos arquivos .cs para compilação
4. Tudo é compilado Os arquivos gerados viram parte do seu assembly
5. Código pronto! Os métodos/classes gerados estão disponíveis para uso

5. Debug e erros típicos

Um dos erros mais comuns de quem começa a escrever geradores é esquecer a palavra-chave partial na classe onde você quer acrescentar código. Se você não marcar como partial, o compilador simplesmente não verá suas alterações. Às vezes o arquivo gerado pode não aparecer na IDE até a primeira rebuild — não se assuste.

Cuidado ao nomear os arquivos gerados: se você der o mesmo Name para todos, eles vão sobrescrever uns aos outros. Dica: incluir o nome da classe no nome do arquivo ajuda — por exemplo, context.AddSource($"{className}_ToString", ...).

Erro de import duplicado de atributos — se você gerar uma classe com um atributo que já existe no projeto principal, vai bater conflito. Melhor extrair atributos necessários para um projeto comum ou só gerar o atributo quando realmente necessário.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION