Programmazione

Union Types in C# 15: insiemi chiusi di tipi con pattern matching esaustivo

Dario Fadda Giugno 11, 2026

Le union types sono finalmente arrivate in C#. Disponibili in anteprima a partire da .NET 11 Preview 2, C# 15 introduce la parola chiave union che consente di dichiarare un tipo che può contenere esattamente uno tra un insieme fisso di tipi, con conversioni implicite e pattern matching esaustivo garantito dal compilatore.

È una delle funzionalità più richieste dalla community da anni, e capire come funziona — e soprattutto perché è stata progettata in questo modo — fa la differenza tra usarla correttamente o incappare negli stessi antipattern che esistevano prima.

Il problema che le union types risolvono

Prima di C# 15, quando un metodo doveva restituire uno tra diversi tipi possibili, le opzioni disponibili erano tutte imperfette:

  • object: nessun vincolo sui tipi effettivamente memorizzati; il chiamante doveva gestire logica difensiva per valori inattesi.
  • Interfacce marcatore o classi base astratte: più sicure, ma non “chiuse” — chiunque poteva implementare l’interfaccia o derivare dalla classe base, quindi il compilatore non poteva mai considerare l’insieme completo dei tipi possibili.
  • Librerie di terze parti come OneOf: funzionali, ma senza supporto diretto del compilatore per l’esaustività.

Un caso tipico è il risultato di un’operazione che può restituire un valore di successo oppure un errore: si potrebbe usare object, un’eccezione, oppure un tipo result wrapper. Nessuna di queste opzioni è soddisfacente perché manca la garanzia statale che tutti i casi siano gestiti.

Le union types risolvono questi problemi dichiarando un insieme chiuso di “case types”: non devono essere correlati tra loro, nessun altro tipo può essere aggiunto, e il compilatore garantisce che le espressioni switch che gestiscono la union siano esaustive — senza aver bisogno di un ramo _ o default.

Sintassi di base

La dichiarazione è minimalista:

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);

Una sola riga dichiara Pet come un nuovo tipo le cui variabili possono contenere un Cat, un Dog o un Bird. Il compilatore fornisce conversioni implicite da ciascun case type:

Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // Dog { Name = Rex }

Pet pet2 = new Cat("Whiskers");
Console.WriteLine(pet2.Value); // Cat { Name = Whiskers }

Il compilatore emette un errore se si cerca di assegnare un tipo che non fa parte dei case types. Questa è la garanzia fondamentale: l’insieme è veramente chiuso a livello di compilazione.

Pattern matching esaustivo

Quando si usa un’istanza di un tipo union non nulla, il compilatore conosce l’insieme completo dei case types, quindi un’espressione switch che li copre tutti è esaustiva — senza bisogno del _ finale:

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
};

Questo è il vantaggio principale: se in futuro si aggiunge un quarto case type a Pet, ogni espressione switch che non lo gestisce produce un avviso del compilatore. I casi mancanti vengono rilevati in fase di compilazione, non a runtime.

I pattern si applicano alla proprietà Value della union, non alla union struct stessa. Questo “unwrapping” è automatico — si scrive Dog d e il compilatore verifica Value internamente. Le eccezioni sono var e _, che si applicano al valore della union stessa.

Per gestire il valore di default (null):

Pet pet = default;

var description = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
    null => "nessun animale",
};
// description è "nessun animale"

Union con corpo e metodi helper

È possibile aggiungere membri helper alla union tramite un corpo, proprio come per qualsiasi altra dichiarazione di tipo. Un esempio pratico è OneOrMore<T>, utile per API che accettano sia un singolo elemento che una collezione:

public union OneOrMore<T>(T, IEnumerable<T>)
{
    public IEnumerable<T> AsEnumerable() => Value switch
    {
        T single => [single],
        IEnumerable<T> multiple => multiple,
        null => []
    };
}

I chiamanti passano la forma che preferiscono, e AsEnumerable() normalizza il risultato:

OneOrMore<string> tags = "dotnet";
OneOrMore<string> moreTags = new[] { "csharp", "unions", "preview" };

foreach (var tag in tags.AsEnumerable())
    Console.Write($"[{tag}] ");
// [dotnet]

foreach (var tag in moreTags.AsEnumerable())
    Console.Write($"[{tag}] ");
// [csharp] [unions] [preview]

Si noti che AsEnumerable gestisce esplicitamente il caso null: lo stato null predefinito della proprietà Value è maybe-null, quindi il compilatore richiede la gestione di questo caso per garantire la correttezza.

Compatibilità con librerie esistenti e scenari avanzati

Per le librerie che già forniscono tipi union-like con proprie strategie di storage (come quelle basate su OneOf), C# 15 prevede un meccanismo di compatibilità: qualsiasi classe o struct con l’attributo [System.Runtime.CompilerServices.Union] viene riconosciuta come tipo union dal compilatore, purché segua il pattern base — costruttori pubblici a parametro singolo e proprietà Value pubblica.

Per scenari ad alte prestazioni dove i case types includono tipi valore, le librerie possono implementare il pattern di accesso non-boxing aggiungendo una proprietà HasValue e metodi TryGetValue. Il tipo union generato dal compilatore usa object? internamente e quindi fa boxing dei tipi valore — per hot path critici conviene valutare i custom union types.

Come provare le union types oggi

Le union types sono disponibili a partire da .NET 11 Preview 2. I passaggi per iniziare sono:

  1. Installare il .NET 11 Preview SDK
  2. Creare o aggiornare un progetto che punta a net11.0
  3. Impostare <LangVersion>preview</LangVersion> nel file di progetto

Poiché UnionAttribute e IUnion non sono ancora inclusi nel runtime nel Preview 2, vanno dichiarati manualmente nel progetto:

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
        AllowMultiple = false)]
    public sealed class UnionAttribute : Attribute;

    public interface IUnion
    {
        object? Value { get; }
    }
}

Una volta aggiunti questi tipi, si possono dichiarare e usare normalmente le union types. Il supporto IDE in Visual Studio sarà disponibile nella prossima build Insiders; il C# DevKit Insiders lo include già.

Il quadro più ampio: la roadmap dell’esaustività

Le union types fanno parte di una strategia più ampia del team C# per portare la verifica dell’esaustività direttamente nel compilatore. Le proposte correlate attualmente in discussione sono:

  • Closed hierarchies: il modificatore closed su una classe impedisce la dichiarazione di classi derivate al di fuori dell’assembly di definizione, consentendo al compilatore di considerare esaustive le espressioni switch sulla gerarchia.
  • Closed enums: un closed enum impedisce la creazione di valori diversi dai membri dichiarati, risolvendo il problema dei valori enum “numerici” inattesi.

Insieme, questi tre meccanismi danno a C# un percorso completo verso la verifica statica dell’esaustività: union types per insiemi chiusi di tipi, closed hierarchies per gerarchie sigillate, closed enums per insiemi fissi di valori.

Conclusione

Le union types in C# 15 non sono un semplice porting delle discriminated union di F#: sono state progettate come aggiunta nativa all’ecosistema C#, composte da tipi esistenti, integrate con il sistema di pattern matching già consolidato, e compatibili con le librerie union-like già diffuse. La garanzia di esaustività del compilatore è il beneficio più concreto: i casi mancanti diventano avvisi a tempo di compilazione, non bug a runtime.

La feature è in preview e il team accetta feedback attivamente su GitHub. Vale la pena esplorarla ora per contribuire alla forma definitiva della feature, prevista per la release di novembre 2026 con .NET 11.

Fonte: Explore union types in C# 15 – .NET Blog

💬 Unisciti alla discussione!


Questo è un blog del Fediverso: puoi trovare quindi questo articolo ovunque con @blog@spcnet.it e ogni commento/risposta apparirà qui sotto.

Se vuoi commentare su Union Types in C# 15: insiemi chiusi di tipi con pattern matching esaustivo, utilizza la discussione sul Forum.
Condividi la tua esperienza, confrontati con altri professionisti e approfondisci i dettagli tecnici nel nostro 👉 forum community