Source Generators in C#

Source Generators in C#: come generare codice a compile-time per ridurre il boilerplate

Source Generators rappresentano una delle evoluzioni più interessanti del linguaggio C# e dell’ecosistema .NET negli ultimi anni. L’idea di fondo è spostare parte della logica che normalmente scriveremmo a mano o gestiremmo a runtime in fase di compilazione, ottenendo codice statico, sicuro e altamente ottimizzato.

Come funzionano

Un Source Generator è un analyzer Roslyn che implementa l’interfaccia ISourceGenerator.
Due metodi fondamentali:

[Generator]
public class MyGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // Qui possiamo registrare callback, syntax receiver ecc.
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // Qui generiamo il codice
        var source = @"
        namespace GeneratedCode {
            public static class HelloWorld {
                public static void SayHello() => System.Console.WriteLine(""Hello from source generator!"");
            }
        }";
        
        context.AddSource("HelloWorld.g.cs", source);
    }
}

Dopo la compilazione, troveremo un file HelloWorld.g.cs in obj/Debug/net6.0/generated/.

Casi d’uso reali

  • DTO e Mapping: generazione automatica di classi di mapping tra entity e DTO.
  • Validazione: generazione di validatori a partire da attributi.
  • Serialization/Deserialization: sostituzione di reflection costosa con codice statico (come fa System.Text.Json).
  • API Client: generazione di client da file OpenAPI/Swagger.
  • INotifyPropertyChanged: implementazione automatica senza scrivere proprietà ripetitive.

Confronto con T4 e Reflection

  • T4 Templates: generano codice a design-time, meno integrati con il compilatore.
  • Reflection: potente ma costosa a runtime.
  • Source Generators: sintesi perfetta, con vantaggi sia in termini di performance che di manutenibilità.

Best Practice

  1. Mantieni il generatore focalizzato: evita logiche eccessivamente complesse.
  2. Testa i generatori: ci sono tool come Verify per validare l’output generato.
  3. Usa partial class: permettono di estendere logica generata senza conflitti.
  4. Gestisci naming e namespace: per evitare collisioni con il codice scritto a mano.

6) Approfondimenti con link esterni

FAQ

Cos’è un Source Generator in C#?
Un Source Generator è una feature introdotta in .NET 5 che permette di generare codice C# durante la compilazione. Si integra con Roslyn e consente di analizzare il codice sorgente e produrre automaticamente classi, metodi o interfacce evitando di scrivere manualmente codice ripetitivo.

Quali sono i vantaggi principali?

  • Eliminazione del boilerplate code
  • Migliore performance a runtime (il codice è già generato e compilato)
  • Maggiore consistenza e riduzione di errori umani
  • Possibilità di estendere framework e librerie con strumenti custom

Posso sostituire Reflection con i Source Generators?
Sì, in molti casi. Ad esempio, invece di usare Reflection per leggere attributi a runtime (con impatti sulle performance), è possibile generare codice statico a compile-time che esegue la stessa logica senza overhead.

I Source Generators sono compatibili con tutti i progetti C#?
Sono supportati da .NET 5 in poi, inclusi progetti .NET Core e .NET Standard. Tuttavia, richiedono il Roslyn compiler, quindi non funzionano in scenari legacy con compilatori più vecchi.

Posso usarli insieme a librerie come Entity Framework o ASP.NET Core?
Sì. Un caso tipico è la generazione automatica di DTO, mapping o client per API. Alcune librerie (ad esempio System.Text.Json) già sfruttano Source Generators per serializzazione più efficiente.

Un esempio pratico: AutoNotify – un Source Generator per implementare automaticamente INotifyPropertyChanged

Uno dei casi più fastidiosi in C# è implementare INotifyPropertyChanged in modelli di ViewModel (ad esempio in WPF, MAUI o WinUI). Scrivere decine di proprietà con lo stesso schema è boilerplate puro:

private string _firstName;
public string FirstName
{
    get => _firstName;
    set
    {
        if (_firstName != value)
        {
            _firstName = value;
            OnPropertyChanged(nameof(FirstName));
        }
    }
}

Con un Source Generator, possiamo:

  • Annotare il campo con un attributo [Observable]
  • Generare automaticamente la property corrispondente
  • Inserire il wiring a INotifyPropertyChanged senza scrivere codice ripetitivo
  • Ridurre errori e migliorare leggibilità

Esempio di utilizzo lato utente:

public partial class Person
{
    [Observable] private string _firstName;
    [Observable] private string _lastName;
}

Codice generato automaticamente dal Source Generator:

partial class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public string FirstName
    {
        get => _firstName;
        set
        {
            if (_firstName != value)
            {
                _firstName = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FirstName)));
            }
        }
    }

    public string LastName
    {
        get => _lastName;
        set
        {
            if (_lastName != value)
            {
                _lastName = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LastName)));
            }
        }
    }
}


Pubblicato

in

da

Tag: