O modificador 'partial' permite dividir uma classe ou método em vários arquivos, facilitando a geração de código.
Ele é ideal para combinar com atributos personalizados, ajudando a localizar exatamente onde o código será gerado.
Pode ser visto como uma forma estática de injeção de dependências, automatizando a adição de código com base em regras.
Reduz a necessidade de código repetitivo e melhorает o desempenho ao eliminar o uso de reflexão.
O projeto GenerateCore implementa este paradigma para tornar a geração de código mais eficinete e manutenível.
- Aplicações no framework
Em regex, serialização e log, o SourceGenerator trabalha em conjunto com 'partial' para criar código dinâmico.
Exemplo de regex 'partial'
partial class Program
{
[GeradoRegex("abc|def", RegexOptions.IgnoreCase)]
private static partial Regex AbcOuDefGeradoRegex();
}
Resultado da geração de regex
partial class Program
{
[global::System.CodeDom.Compiler.GeradoCodigoAttribute("System.Text.RegularExpressions.Gerador", "8.0.14.7010")]
private static partial global::System.Text.RegularExpressions.Regex AbcOuDefGeradoRegex()
=> global::System.Text.RegularExpressions.Gerado.AbcOuDefGeradoRegex_0.Instancia;
}
- Elementos do paradigma 'partial'
1. Atributos
Atributos são usados para marcar locais onde o código será gerado automaticamente.
Eles devem ser nomeados de forma consistente com o gerador de código correspondente.
Pode transmitir parâmetros para o gerador, que os utiliza para ajustar a saída.
2. Filtros
Interface ISyntaxFilter é usada para filtrar nós sintáticos.
interface ISyntaxFilter
{
bool Correspondencia(NóSintático nó, CancellationToken cancelamento);
}
class FiltroSintático(bool éParcial, params SyntaxKind[] tipos)
: ISyntaxFilter;
3. Fonte do Gerador
IGeneratorSource define como o código será gerado e nomeado.
interface IGeneratorSource
{
string NomeArquivoGerado { get; }
GeradorSintático Gerar();
}
4. Transformação
Transformação converte contexto de atributos em fonte de geração.
interface IGeneratorTransform<tsource>
{
TSource? Transformar(ContextoSyntaxGeneratorAtributo contexto, CancellationToken cancelamento);
}
</tsource>
5. Executor
IGeneratorExecutor é responsável por executar a geração de código.
interface IGeneratorExecutor<tsource>
{
void Executar(ContextoProduçãoFonte contexto, TSource fonte);
}
</tsource>
6. Gerador de Valores
ValuesGenerator ajuda a desenvolver geradores de código simplificando a lógica de negócio.
class GeradorValores<tsource>(
string nomeAtributo,
ISyntaxFilter filtro,
ISyntaxTransform<tsource> transformação,
ISyntaxExecutor<tsource> executor);
</tsource></tsource></tsource>
- Implementando um gerador com ValuesGenerator
1. Código do gerador SaudacaoGenerator
class SaudacaoGenerator()
: ValuesGenerator<saudacaosource>(
"GenerateCoreTests.Saudacao.SaudacaoGeneratorAttribute",
new FiltroSintático(true, SyntaxKind.DeclaraçãoClasse),
new SaudacaoTransformação(),
new ExecutorGerador<saudacaosource>())
{
}
</saudacaosource></saudacaosource>
2. Atributo SaudacaoGeneratorAttribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
class SaudacaoGeneratorAttribute : Attribute
{
}
3. Transformação SaudacaoTransformação
class SaudacaoTransformação : IGeneratorTransform<saudacaosource>
{
public SaudacaoSource? Transformar(ContextoSyntaxGeneratorAtributo contexto, CancellationToken cancelamento)
{
if (contexto.NóAlvo é DeclaraçãoClasseSintática tipo e contexto.SímboloAlvo é INamedTypeSymbol símbolo)
return new(tipo, símbolo);
return null;
}
}
</saudacaosource>
4. Fonte de geração SaudacaoSource
class SaudacaoSource(DeclaraçãoClasseSintática tipo, INamedTypeSymbol símbolo)
: IGeneratorSource
{
private readonly DeclaraçãoClasseSintática _tipo = tipo;
private readonly INamedTypeSymbol _símbolo = símbolo;
public string NomeArquivoGerado
=> $"{_símbolo.ToDisplayString()}.Saudacao.g.cs";
public GeradorSintático Gerar()
{
var construtor = GeradorSintático.Clone(_tipo);
var método = MétodoGerado();
construtor.AdicionarMembro(método);
return construtor;
}
private static MethodDeclarationSyntax MétodoGerado()
{
var nome = SyntaxFactory.NomeIdentificador("nome");
var expressão = GeradorSintático.Interpolação()
.Adicionar("Saudação: '")
.Adicionar(nome)
.Adicionar("'")
.Construir();
return GeradorSintático.TipoVazio.Método("DigaoOi", GeradorSintático.TipoString.Parâmetro(nome.Identificador))
.Público()
.Estático()
.ParaConstrutor()
.Adicionar(SyntaxFactory.NomeIdentificador("Console").Acesso("WriteLine").Invocação([expressão]))
.Terminar();
}
}
5. Código de teste
namespace GenerateCoreTests.Saudacao;
[SaudacaoGenerator]
public partial class SaudacaoTestes;
6. Resultado da geração
// <auto-generated></auto-generated>
namespace GenerateCoreTests.Saudacao;
partial class SaudacaoTestes
{
public static void DigaoOi(string nome)
{
Console.WriteLine($"Saudação: '{nome}'");
}
}
- Recursos adicionais do GenerateCore
1. Aálise de símbolos de compilação
Use SymbolTypeDescriptor e SymbolTypeBuilder para analisar código existente.
2. Ferramentas para atributos
SymbolAttributeHelper ajuda a extrair e validar atributos de código.
- Testando SourceGenerator
1. Usando Debugger.Launch
Inclua Debugger.Launch() para testar interativamente no VS.
2. Testando com Microsoft.CodeAnalysis.Analyzer.Testing
Esta biblioteca permite testes unitários, mas pode ser complexa.
3. Testando com SyntaxScripting
SyntaxScripting é uma ferramenta simples para testes de geração de código.
var código = "partial class Saudação;";
var resultado = SyntaxTreeScript.Criar()
.Usando("System")
.Gerar<saudacaogenerator>(código)
.ObterResultado();
var árvore = resultado.ArvoreGerada.FirstOrDefault();
Assert.NotNull(árvore);
var diagnósticos = resultado.Diagnósticos;
Assert.Empty(diagnósticos);
</saudacaogenerator>
- Conclusão
O paradigma 'partial' (com GenerateCore e EasySyntax) simplifica o desenvolvimento de SourceGenerator.
Ele permite separar a lógica de geração em componentes independentes, melhorando a legibilidade e testabilidade.
O SyntaxScripting é uma excelente opção para testar e executar geradores de código.