DEV Community

Angelo Belchior
Angelo Belchior

Posted on • Edited on

.NET Source Generators: gerando código em tempo de escrita de código!

Eu sei, é confuso...

E acredito que se você leu atentamente meu post anterior .NET 8, JIT e AOT, percebeu que eu citei os Sources Generators como uma estratégia importante para que seja possível ter um melhor suporte ao AOT.

Sendo assim, resolvi explorar um pouco mais a fundo esse conteúdo. Mas não se preocupe, eu vou explicar ponto a ponto e tenho certeza de que no final dessa leitura você vai curtir demais os Sources Generators. Aliás, é bem provável que você já os utilize e não percebeu.

Para começar, precisamos primeiro explicar...

O que é o Roslyn?

Logo do Roslyn

The .NET Compiler Platform!

A Plataforma de Compilação do .Net!

É assim que o time da Microsoft apresenta o Roslyn.

Roslyn é a implementação dos compiladores do csharp e do vb.net, simples assim. É um projeto de código fonte aberto com muito apoio da comunidade. E isso é realmente incrível.

Além dos compiladores em si, ele traz um conjunto de ferramentas para análise de código e por ser totalmente extensível é possível consumir suas inúmeras APIs e criar ferramentas que possam inspecionar, modificar e efetuar refatorações no seu código.

Essa abordagem o torna uma plataforma de compilação como um serviço.

Compiler as a Service. Que coisa chique!

E o mais interessante disso tudo é que o Roslyn é escrito em csharp.
Exatamente isso: O compilador do csharp é escrito em csharp.
Não acredita? Clica aqui então!

Se você parar para analisar, vai perceber que tanto o Visual Studio quanto o Visual Studio Code com o Dev Kit tem as mesmas ferramentas de análise de código e refatoração. Isso se deve ao fato de utilizarem o mesmo core de compilação: Roslyn. Isso demonstra o quão extensível e flexível ele é!

Se você quiser, pode criar sua própria IDE com um suporte extraordinário a csharp e vb.net! Não é incrível?

O Rider da Jetbrains utiliza outra abordagem, espero um dia fazer um post para falar um pouco mais sobre ele.

Deixo claro aqui que minha ideia não é ir muito a fundo nesse universo de compiladores. Pretendo um dia fazer um post falando especificamente sobre isso e, claro, utilizar o Roslyn como exemplo.

Porém, agora, eu quero apenas fazer uma sutil introdução de como algumas coisas funcionam por debaixo do capô para que fique mais simples entender como os Source Generators atuam no seu código. O Roslyn é parte fundamental disso.

Sendo assim, imagine que ele é um sistema que expõe inúmeras APIs organizadas em grupos:

  • APIs do Compilador: Essas APIs expõem modelos que correspondem ao processo de análise sintática e semântica. Cada linguagem vai ter sua API.
  • APIs de Diagnóstico: Temos aqui APIs que são acionadas no momento da análise do código, onde o compilador pode produzir um conjunto de informações que abrange desde sintaxe inválida, problemas de semântica e alertas.
  • APIs de Script: É possível usar as APIs de script para executar snippets de código. O REPL interativo usa essas APIs permitindo que você utilize o csharp como a linguagem de script, executando códigos de maneira interativa enquanto programa.
  • APIs de Workspace: Esta camada de APIs abrange dados referentes aos projetos de uma solução, assim como informações utilizadas por ferramentas de análise de código, refatoração e IDEs. Alguns exemplos englobam as APIs de Localização de todas as Referências, Formatação e Geração de Código..

Fases de compilação é exposição de APIs do Roslyn

A imagem acima mostra as fazes de compilação e exposição das APIs do Roslyn. Cada caixinha dessas é um universo a parte, daria até para escrever um post específico para cada uma delas, de tanta informação que existe. Porém eu recomendo muito a leitura desse artigo: Conceitos e modelo de objeto do SDK do .NET Compiler Platform. Com certeza vai sanar suas dúvidas!

Essa é a minha introdução singela ao Roslyn. Mas não se engane, ele faz muito mais coisas do que eu citei aqui. Pra fins didáticos eu quis apenas trazer a ideia de que ele disponibiliza várias APIs que são utilizadas pelas IDEs e ferramentas externas ou internas. Fique com isso na sua cabeça!

Sendo assim, vamos começar...


O que são Source Generators?

Source Generators é uma feature que veio junto com o csharp 9.
A ideia é permitir que códigos sejam gerados automagicamente durante a compilação.
Lembrando que, a cada palavra escrita no seu código csharp, uma compilação direcionada é feita, isto é, todo código que depender daquele trecho onde foi feita uma alteração, será recompilado.

Esse processo de compilação gera eventos e as IDEs capturam essas informações e aplicam regras de formatação, efetuam análises de semântica e validações, dentre outras coisas. Um exemplo singelo disso é quando você escreve algo como items.Lenght. Nenhum ser humano sóbrio consegue escrever a palavra Length corretamente logo de primeira. A gente vai errar! O compilador vai analisar! A IDE vai notificar! E a gente vai corrigir. Esse é o ciclo.

A partir do momento em que o compilador emite notificações de alteração de código, isso abre oportunidades para que, além das IDEs, outras ferramentas possam interagir com o seu projeto. É ai que entram os Source Generators.

Tá ok... acho que estou entendendo. Mas o que é de fato esse Source Generator?

Ótima pergunta!

O Source Generator nada mais é do que uma lib que você instala via nuget. Simples assim.

Essa biblioteca precisa referenciar alguns pacotes específicos que vão dar suporte a criação dos códigos durante a escrita de código:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

Acima temos um exemplo de um *.csproj simples que suporta a criação de Source Generators. Para mais informações clique aqui!

Ao instalar ou referenciar um Source Generator ao seu projeto, ele entra num processo de execução que é orquestrado pelo Roslyn:

Fluxo de geração de código em tempo de escrita de código

A imagem acima mostra exatamente como esse processo funciona:

  • Você altera seu código;
  • O compilador efetua uma análise e caso exista alguma referência para um Source Generator ele o aciona;
  • O Source Generator inspeciona seu código e em seguida gera código adicional com base em determinadas regras ou modelos. Esse código fica num arquivo físico e é incorporado ao projeto.

Dessa forma é gerado código em tempo de escrita de código!

Sacou?

Acho que ainda não, né? Mas com os exemplos a seguir vai ficar claro. Prometo!
Aliás, pega um café... porque a coisa vai ficar mais interessante a partir de agora....


Vamos falar sobre Regex

Calma, o post ainda é sobre Source Generators! Eu só vou usar o Regex como exemplo para demonstrar todo o poder dessa feature.

Em resumo, o Regex, ou expressão regular, é uma sequência de caracteres que forma um padrão de pesquisa. Esse padrão de pesquisa é usado principalmente para correspondência de strings em operações de busca, extração ou manipulação de texto.

Regex é algo muito utilizado. E depois que o chatGPT foi criado todas as dificuldades que a gente tinha para criar uma, foram resolvidas. 🤓

Feita a devida apresentação, vamos colocar em prática o uso do Regex.
Vamos criar um sistema que recebe um texto e extrai todos os e-mails. Algo simples:

using System.Text.RegularExpressions;

var regex = new Regex(@"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", RegexOptions.Compiled);

var texto = 
"""
Este é um exemplo de texto com alguns e-mails, como contato@example.com e outro@email.com.br. 
Além disso, temos mais um: email@teste.com.";
""";

ExtrairEmails(regex, texto);
return;

static void ExtrairEmails(Regex regex, string texto)
{
    var matches = regex.Matches(texto);

    Console.WriteLine("E-mails encontrados:");
    foreach (Match match in matches)
    {
        Console.WriteLine(match.Value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ao executar o resultado é esse:

/Volumes/SSD/Git/SourceGeneratorSample/SourceGeneratorSample/bin/Debug/net7.0/SourceGeneratorSample 
E-mails encontrados:
contato@example.com
outro@email.com.br
email@teste.com
Enter fullscreen mode Exit fullscreen mode

O código acima é para fins didáticos apenas e foi gerado via chatGPT sem a mínima responsabilidade.
Minha intenção não é explicar a sintaxe do regex. Caso queira saber mais sobre, acesse esse post: Regular expression - Wikipedia

Resumidamente o que temos é a definição do padrão de busca de e-mails.
Utilizamos a classe Regex passando esse padrão.
Em seguida, procuramos dentro de um determinado texto, quais conjuntos de caracteres dão match (combinam) com o padrão informado, e imprimimos o resultado. Simples, simples, simples!

Agora vamos entender em como o csharp resolve isso internamente!

Analisando esse processo, podemos notar que num determinado momento vai ser preciso fazer um parser desse regex. Um parser nada mais é do que uma rotina responsável por analisar e interpretar padrões definidos por expressões regulares, no nosso caso esse texto: @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}". O resultado gerado por esse parser é conhecido com árvore sintática. Essa árvore representa a estrutura hierárquica do regex, mostrando como os diferentes elementos (caracteres que formam o padrão) estão relacionados entre si. A partir dessa árvore, o csharp pode interpretar ou compilar essas instruções. Mais abaixo eu mostro um trecho do código que faz essa compilação (geração de IL).

Feito esse parser é necessário criar um mecanismo que receba um determinado texto e execute os comandos gerados a partir dessa interpretação ou compilação.

Com esse mecanismo criado basta invocá-lo para obter o resultado.

Só que tem alguns "poréns"...

O processo de Regex utiliza muito a geração dinâmica de código. Se a gente for analisar o código fonte da classe RegexCompiler podemos notar logo de cara o gigantesco uso de IL e muito reflection:

internal static MethodInfo     _chartolowerM; 
internal static MethodInfo     _getcharM; 
internal static MethodInfo     _crawlposM; 
internal static MethodInfo     _charInSetM;
internal static MethodInfo     _getCurrentCulture;
internal static MethodInfo     _getInvariantCulture;
internal static MethodInfo     _checkTimeoutM;
#if DBG
    internal static MethodInfo     _dumpstateM;
#endif

internal ILGenerator     _ilg;

//.....

/*
 * Pops an element off the tracking stack (leave it on the operand stack)
 */
internal void PopTrack() {
    _ilg.Emit(OpCodes.Ldloc_S, _trackV);
    _ilg.Emit(OpCodes.Ldloc_S, _trackposV);
    _ilg.Emit(OpCodes.Dup);
    _ilg.Emit(OpCodes.Ldc_I4_1);
    _ilg.Emit(OpCodes.Add);
    _ilg.Emit(OpCodes.Stloc_S, _trackposV);
    _ilg.Emit(OpCodes.Ldelem_I4);
}
Enter fullscreen mode Exit fullscreen mode

O código acima é apenas uma pequena parte dessa classe, mas já notamos o uso do MethodInfo e do ILGenerator. É tanto _ilg.Emit espalhado pela classe, que eu desisti de tentar entender como isso está funcionando. Eu particularmente curto muito brincar com o IL.

Mas o AOT não :(

No post anterior eu explico o motivo pelo qual AOT e código gerado dinamicamente não combinam.

E é agora, o tão aguardado momento em que, de fato, vamos conhecer esse tal Source Generators.

Tardô, mas num faiô!

Primeiro, vamos começar avaliando os analisadores de código disponíveis em nossa IDE. Eu uso o Rider, mas é possível ter o mesmo resultado em qualquer ferramenta.

Olha que interessante:

Imagem do Rider sugerindo o uso do Source Generator do Regex

O Rider me sugeriu transformar esse regex em um GeneratedRegexAttribute.

Como o próprio nome diz, a ideia é utilizar um Source Generator que vai avaliar o padrão informado @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" e criar um código csharp para a execução dessa busca por e-mails.

Olha que coisa incrível! Ao invés de existir um parser para o regex e a criação em tempo de execução de um mecanismo de busca, compilado ou interpretado, nós teremos um código pronto, gerado única e exclusivamente para esse fim.

E eu nem preciso dizer que esse processo acaba sendo mais performático do que o anterior, já que não vamos despender tempo para criação do parser, interpretadores, compilações e outras coisas.

E outra, a partir do momento que o Source Generator cria esse código, nós podemos debuga-lo!
Isso mesmo! É possível colocar um break point e entender o processo todo.

Isso é um sonho realizado: Debugar Regex! Correu uma lágrima do meu olho agora...

E como ficou esse código gerado pelo Source Generator?

using System.Text.RegularExpressions;

var regex = MeusRegex.BuscaPorEmails();

var texto = 
"""
Este é um exemplo de texto com alguns e-mails, como contato@example.com e outro@email.com.br. 
Além disso, temos mais um: email@teste.com.";
""";

ExtrairEmails(regex, texto);
return;

static void ExtrairEmails(Regex regex, string texto)
{
    var matches = regex.Matches(texto);

    Console.WriteLine("E-mails encontrados:");
    foreach (Match match in matches)
    {
        Console.WriteLine(match.Value);
    }
}

public partial class MeusRegex
{
    [GeneratedRegex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")]
    public static partial Regex BuscaPorEmails();
}
Enter fullscreen mode Exit fullscreen mode

Surpresaaaaa

Não, esse não é o código gerado pelo Source Generator! É como o código ficou para utilizar o código gerado pelo Source Generator!

Mas precisamos esclarecer um ponto antes de ir, de fato, ao código gerado automagicamente.

Partial Class!?!

Se você não sabe o que é uma partial class recomendo muito a leitura desse artigo. Partial Classes and Methods - C# Programming Guide - C# | Microsoft Learn .

Para entender como os Sources Generators funcionam é muito importante entender como o partial class funciona.

Quando a gente cria um método partial dentro de uma classe partial e decora esse método com o atributo GeneratedRegex informando qual é a regex, o Source Generator é acionado. Ele vai avaliar o regex informado e transformá-lo em código csharp.

Ok, mas onde fica esse arquivo gerado pelo Source Generator?

Arquivo gerado

Basicamente, dentro da estrutura do projeto, vai existir uma pasta chamada Source Generators, ou algo do tipo, dependendo da sua IDE. E é aí que estarão todos os arquivos gerados por Source Generators.
Podemos notar que o arquivo termina com *.g.cs. Esse "g" significa generated e serve justamente para identificar que o conteúdo dele foi gerado por alguma ferramenta, externa ou não.

Note que dentro desse arquivo vamos ter uma classe partial chamada MeusRegex com a implementação do método partial BuscaPorEmails.
Em outras palavras, no nosso arquivo program.cs temos a criação dessa classe com algumas definições parciais, e no arquivo gerado automagicamente temos as implementações concretas!

E nem perca seu tempo tentando alterar esse arquivo. Ele vai ser sempre reescrito pelo Source Generator.

E agora só falta ver o código gerado.

Respira fundo...

// <auto-generated/>
#nullable enable
#pragma warning disable CS0162 // Unreachable code
#pragma warning disable CS0164 // Unreferenced label
#pragma warning disable CS0219 // Variable assigned but never used

partial class MeusRegex
{
    /// <remarks>
    /// Pattern explanation:<br/>
    /// <code>
    /// ○ Match a character in the set [%+-.0-9A-Z_a-z] atomically at least once.<br/>
    /// ○ Match '@'.<br/>
    /// ○ Match a character in the set [-.0-9A-Za-z] greedily at least once.<br/>
    /// ○ Match '.'.<br/>
    /// ○ Match a character in the set [A-Za-z] atomically at least twice.<br/>
    /// </code>
    /// </remarks>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "7.0.9.1910")]
    public static partial global::System.Text.RegularExpressions.Regex BuscaPorEmails() => global::System.Text.RegularExpressions.Generated.BuscaPorEmails_0.Instance;
}

namespace System.Text.RegularExpressions.Generated
{
    using System;
    using System.CodeDom.Compiler;
    using System.Collections;
    using System.ComponentModel;
    using System.Globalization;
    using System.Runtime.CompilerServices;
    using System.Text.RegularExpressions;
    using System.Threading;

    /// <summary>Custom <see cref="Regex"/>-derived type for the BuscaPorEmails method.</summary>
    [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "7.0.9.1910")]
    file sealed class BuscaPorEmails_0 : Regex
    {
        /// <summary>Cached, thread-safe singleton instance.</summary>
        internal static readonly BuscaPorEmails_0 Instance = new();

        /// <summary>Initializes the instance.</summary>
        private BuscaPorEmails_0()
        {
            base.pattern = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
            base.roptions = RegexOptions.None;
            ValidateMatchTimeout(Utilities.s_defaultTimeout);
            base.internalMatchTimeout = Utilities.s_defaultTimeout;
            base.factory = new RunnerFactory();
            base.capsize = 1;
        }

        /// <summary>Provides a factory for creating <see cref="RegexRunner"/> instances to be used by methods on <see cref="Regex"/>.</summary>
        private sealed class RunnerFactory : RegexRunnerFactory
        {
            /// <summary>Creates an instance of a <see cref="RegexRunner"/> used by methods on <see cref="Regex"/>.</summary>
            protected override RegexRunner CreateInstance() => new Runner();

            /// <summary>Provides the runner that contains the custom logic implementing the specified regular expression.</summary>
            private sealed class Runner : RegexRunner
            {
                /// <summary>Scan the <paramref name="inputSpan"/> starting from base.runtextstart for the next match.</summary>
                /// <param name="inputSpan">The text being scanned by the regular expression.</param>
                protected override void Scan(ReadOnlySpan<char> inputSpan)
                {
                    // Search until we can't find a valid starting position, we find a match, or we reach the end of the input.
                    while (TryFindNextPossibleStartingPosition(inputSpan) &&
                           !TryMatchAtCurrentPosition(inputSpan) &&
                           base.runtextpos != inputSpan.Length)
                    {
                        base.runtextpos++;
                        if (Utilities.s_hasTimeout)
                        {
                            base.CheckTimeout();
                        }
                    }
                }

                /// <summary>Search <paramref name="inputSpan"/> starting from base.runtextpos for the next location a match could possibly start.</summary>
                /// <param name="inputSpan">The text being scanned by the regular expression.</param>
                /// <returns>true if a possible match was found; false if no more matches are possible.</returns>
                private bool TryFindNextPossibleStartingPosition(ReadOnlySpan<char> inputSpan)
                {
                    int pos = base.runtextpos;
                    char ch;

                    // Any possible match is at least 6 characters.
                    if (pos <= inputSpan.Length - 6)
                    {
                        // The pattern begins with an atomic loop for a character in the set [%+-.0-9A-Z_a-z], followed by the character '@'.
                        // Search for the literal, and then walk backwards to the beginning of the loop.
                        while (true)
                        {
                            ReadOnlySpan<char> slice = inputSpan.Slice(pos);

                            int i = slice.IndexOf('@');
                            if (i < 0)
                            {
                                break;
                            }

                            int prev = i - 1;
                            while ((uint)prev < (uint)slice.Length && ((ch = slice[prev]) < '{' && ("\0\0栠Ͽ\ufffe蟿\ufffe߿"[ch >> 4] & (1 << (ch & 0xF))) != 0))
                            {
                                prev--;
                            }

                            if ((i - prev - 1) < 1)
                            {
                                pos += i + 1;
                                continue;
                            }

                            base.runtextpos = pos + prev + 1;
                            base.runtrackpos = pos + i;
                            return true;
                        }
                    }

                    // No match found.
                    base.runtextpos = inputSpan.Length;
                    return false;
                }

                /// <summary>Determine whether <paramref name="inputSpan"/> at base.runtextpos is a match for the regular expression.</summary>
                /// <param name="inputSpan">The text being scanned by the regular expression.</param>
                /// <returns>true if the regular expression matches at the current position; otherwise, false.</returns>
                private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
                {
                    int pos = base.runtextpos;
                    int matchStart = pos;
                    char ch;
                    int charloop_starting_pos = 0, charloop_ending_pos = 0;
                    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

                    // Skip loop already matched in TryFindNextPossibleStartingPosition.
                    pos = base.runtrackpos;
                    slice = inputSpan.Slice(pos);

                    // Advance the next matching position.
                    if (base.runtextpos < pos)
                    {
                        base.runtextpos = pos;
                    }

                    // Match '@'.
                    if (slice.IsEmpty || slice[0] != '@')
                    {
                        return false; // The input didn't match.
                    }

                    // Match a character in the set [-.0-9A-Za-z] greedily at least once.
                    //{
                        pos++;
                        slice = inputSpan.Slice(pos);
                        charloop_starting_pos = pos;

                        int iteration = 0;
                        while ((uint)iteration < (uint)slice.Length && ((ch = slice[iteration]) < '{' && ("\0\0怀Ͽ\ufffe߿\ufffe߿"[ch >> 4] & (1 << (ch & 0xF))) != 0))
                        {
                            iteration++;
                        }

                        if (iteration == 0)
                        {
                            return false; // The input didn't match.
                        }

                        slice = slice.Slice(iteration);
                        pos += iteration;

                        charloop_ending_pos = pos;
                        charloop_starting_pos++;
                        goto CharLoopEnd;

                        CharLoopBacktrack:

                        if (Utilities.s_hasTimeout)
                        {
                            base.CheckTimeout();
                        }

                        if (charloop_starting_pos >= charloop_ending_pos ||
                            (charloop_ending_pos = inputSpan.Slice(charloop_starting_pos, charloop_ending_pos - charloop_starting_pos).LastIndexOf('.')) < 0)
                        {
                            return false; // The input didn't match.
                        }
                        charloop_ending_pos += charloop_starting_pos;
                        pos = charloop_ending_pos;
                        slice = inputSpan.Slice(pos);

                        CharLoopEnd:
                    //}

                    // Match '.'.
                    if (slice.IsEmpty || slice[0] != '.')
                    {
                        goto CharLoopBacktrack;
                    }

                    // Match a character in the set [A-Za-z] atomically at least twice.
                    {
                        pos++;
                        slice = inputSpan.Slice(pos);
                        int iteration1 = 0;
                        while ((uint)iteration1 < (uint)slice.Length && char.IsAsciiLetter(slice[iteration1]))
                        {
                            iteration1++;
                        }

                        if (iteration1 < 2)
                        {
                            goto CharLoopBacktrack;
                        }

                        slice = slice.Slice(iteration1);
                        pos += iteration1;
                    }

                    // The input matched.
                    base.runtextpos = pos;
                    base.Capture(0, matchStart, pos);
                    return true;
                }
            }
        }

    }

    /// <summary>Helper methods used by generated <see cref="Regex"/>-derived implementations.</summary>
    [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "7.0.9.1910")]
    file static class Utilities
    {
        /// <summary>Default timeout value set in <see cref="AppContext"/>, or <see cref="Regex.InfiniteMatchTimeout"/> if none was set.</summary>
        internal static readonly TimeSpan s_defaultTimeout = AppContext.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") is TimeSpan timeout ? timeout : Regex.InfiniteMatchTimeout;

        /// <summary>Whether <see cref="s_defaultTimeout"/> is non-infinite.</summary>
        internal static readonly bool s_hasTimeout = s_defaultTimeout != Timeout.InfiniteTimeSpan;
    }
}
Enter fullscreen mode Exit fullscreen mode

Não, eu não vou explicar o que esse código faz. Mesmo porque o objetivo dele é encontrar e-mails em um texto e retornar. E isso ele continua fazendo maravilhosamente bem. E de forma extremamente rápida.

Eu poderia inclusive fazer um benchmark entre o código que não usa Source Generator e esse ai de cima. Porém, já fizeram isso. E fizeram de uma forma espetacular: Regular Expression Improvements in .NET 7 - .NET Blog (microsoft.com).
Esse é o tipo de post que beira o estado da arte. O Stephen Toub destrincha cada detalhe do uso de Source Generators no Regex. Fora que tem outras questões que envolvem performance também e que ele detalha de maneira extraordinária. O cara é sensacional!


Eu fiz questão de utilizar o exemplo do Regex para reforçar a importância dos Source Generators para o tipo de compilação AOT.

Fiz um exemplo bem simplório, mas acredito que deu pra entender o quão fantástico é a ideia do Source Generator.

Utilizando essa abordagem, o código gerado é um código csharp puro, focado em executar apenas aquilo que foi configurado. Sem IL, sem Relflection, sem código dinâmico e com muita performance. Mas muita mesmo! Se você leu o post do Stephen Toub, com certeza percebeu isso!

Agora sim o AOT curtiu \o/


Acredito que agora você conseguiu entender o quão genial é essa ideia de Source Generators e o quanto isso vai facilitar as nossas vidas.

Inclusive, recomendo muito esse repositório: https://github.com/amis92/csharp-source-generators. Aqui são listados centenas de Source Generators e é uma fonte maravilhosa de aprendizado. Mais recomendado do que pão de queijo.

E claro, não podemos deixar de citar a documentação da Microsoft sobre o assunto:
https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview

Eu não mostrei como se cria um Source Generator mas recomendo muito esse artigo aqui:
https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/

Era isso. Espero que tenham gostado!

Até a próxima e bebam água.

Top comments (0)