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
- Mantieni il generatore focalizzato: evita logiche eccessivamente complesse.
- Testa i generatori: ci sono tool come
Verify
per validare l’output generato. - Usa partial class: permettono di estendere logica generata senza conflitti.
- Gestisci naming e namespace: per evitare collisioni con il codice scritto a mano.
6) Approfondimenti con link esterni
- Documentazione ufficiale Microsoft: Source Generators
- Blog di Andrew Lock: A deep dive into C# Source Generators
- GitHub Examples: dotnet/roslyn-sdk
- Esempi pratici su
System.Text.Json
con Source Generators: Docs
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)));
}
}
}
}