Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Un tipo di unione rappresenta un valore che può essere uno dei diversi tipi di maiuscole e minuscole. Le unioni forniscono conversioni implicite da ogni tipo di caso, criteri di ricerca completi e rilevamento avanzato dei valori Null. Usare la union parola chiave per dichiarare un tipo di unione:
public union Pet(Cat, Dog, Bird);
Questa dichiarazione crea un'unione Pet con tre tipi di maiuscole e minuscole: Cat, Doge Bird. È possibile assegnare qualsiasi valore di tipo case a una Pet variabile. Il compilatore garantisce che switch le espressioni coprono tutti i tipi di maiuscole e minuscole.
Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle versioni di anteprima pubblica per la prossima versione del linguaggio di programmazione.
La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.
Suggerimento
Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.
Dichiarare un'unione quando un valore deve essere esattamente uno di un set fisso di tipi e si vuole che il compilatore applichi che ogni possibilità venga gestita. Gli scenari comuni includono:
-
Result-or-error restituisce: un metodo restituisce un valore di esito positivo o un valore di errore e il chiamante deve gestire entrambi. Un'unione come
union Result(Success, Error)rende esplicito il set di risultati. -
Invio di messaggi o comandi: un sistema elabora un set chiuso di tipi di messaggio. Un'unione garantisce che i nuovi tipi di messaggio generino avvisi in fase di compilazione in ogni
switchoggetto che non li gestisce ancora. - Sostituzione di interfacce marcatori o classi di base astratte: se si usa un'interfaccia o una classe astratta esclusivamente per raggruppare i tipi di criteri di ricerca per i criteri di ricerca, un'unione offre un controllo completo senza richiedere ereditarietà o membri condivisi.
Un'unione è diversa da altre dichiarazioni di tipo in modi importanti:
- A differenza di un
classoggetto ostruct, un'unione non definisce nuovi membri dati. Compone invece i tipi esistenti in un set chiuso di alternative. - A differenza di ,
interfaceun'unione viene chiusa, si definisce l'elenco completo dei tipi di maiuscole e minuscole nella dichiarazione e il compilatore usa tale elenco per i controlli di completezza. - A differenza di ,
recordun'unione non aggiunge un comportamento di uguaglianza, clonazione o decostruzione. Un'unione è incentrata su "qual è il caso?" invece di "quali campi ha?"
Importante
In .NET 11 Preview 2 il runtime non include l'interfaccia UnionAttribute e IUnion . Per usare i tipi di unione, è necessario dichiararli manualmente. Per visualizzare le dichiarazioni necessarie, vedere Implementazione dell'Unione.
Dichiarazioni di unione
Una dichiarazione di unione specifica un nome e un elenco di tipi di maiuscole e minuscole:
public union Pet(Cat, Dog, Bird);
I tipi case possono essere qualsiasi tipo che converte in object, incluse classi, struct, interfacce, parametri di tipo, tipi nullable e altre unioni. Gli esempi seguenti mostrano diverse possibilità di tipo case:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);
public union IntOrString(int, string);
Quando un tipo case è un tipo valore (ad esempio int), il valore viene sottoposto a boxing quando viene archiviato nella proprietà dell'unione Value . Le unioni archiviano il contenuto come singolo object? riferimento.
Una dichiarazione di unione può includere un corpo con membri aggiuntivi, proprio come uno struct, soggetto ad alcune restrizioni. Le dichiarazioni di unione non possono includere campi di istanza, proprietà automatiche o eventi simili a campi. Non è inoltre possibile dichiarare costruttori pubblici con un singolo parametro, perché il compilatore genera tali costruttori come membri di creazione dell'unione:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
Conversioni di unioni
Esiste una conversione di unione implicita da ogni tipo di case al tipo di unione. Non è necessario chiamare un costruttore in modo esplicito:
static void BasicConversion()
{
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // output: Dog { Name = Rex }
Pet pet2 = new Cat("Whiskers");
Console.WriteLine(pet2.Value); // output: Cat { Name = Whiskers }
}
Le conversioni unioni funzionano chiamando il costruttore generato corrispondente. Se esiste un operatore di conversione implicita definito dall'utente per lo stesso tipo, l'operatore definito dall'utente ha la priorità sulla conversione dell'unione. Per informazioni dettagliate sulla priorità di conversione, vedere la specifica della lingua.
Una conversione di unione in uno struct di unione nullable (T?) funziona anche quando T è un tipo di unione:
static void NullableUnionExample()
{
Pet? maybePet = new Dog("Buddy");
Pet? noPet = null;
Console.WriteLine(Describe(maybePet)); // output: Dog: Buddy
Console.WriteLine(Describe(noPet)); // output: no pet
static string Describe(Pet? pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
}
Corrispondenza unione
Quando il criterio corrisponde a un tipo di unione, i criteri si applicano alla proprietà dell'unione, non al valore di Value unione stesso. Questo comportamento di annullamento del wrapping significa che l'unione è trasparente per la corrispondenza dei criteri:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Due modelli sono eccezioni a questa regola: il var modello e il criterio di eliminazione _ si applicano al valore di unione stesso, non alla relativa Value proprietà. Usare var per acquisire il valore di unione quando GetPet() restituisce un valore Pet? (Nullable<Pet>):
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
Nei modelli logici ogni ramo segue singolarmente la regola di annullamento del wrapping. Il modello seguente verifica che l'oggetto Pet? non sia Null e che Value non sia Null:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}
Annotazioni
Poiché i modelli si applicano a Value, un modello come pet is Pet in genere non corrisponde, poiché Pet viene testato sul contenuto dell'unione, non sull'unione stessa.
Corrispondenza null
Per le unioni struct, il null criterio controlla se Value è Null:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Per le unioni basate su classi, null ha esito positivo quando il riferimento union stesso è Null o la relativa Value proprietà è null:
Result<string>? result = null;
if (result is null) { /* true — the reference is null */ }
Result<string> empty = new Result<string>((string?)null);
if (empty is null) { /* true — Value is null */ }
Per i tipi struct di unione nullable (Pet?), null ha esito positivo quando il wrapper nullable non ha alcun valore o quando l'unione Value sottostante è Null.
Esaustività dell'Unione
Un'espressione switch è esaustiva quando gestisce tutti i tipi di maiuscole e minuscole di un'unione. Il compilatore avvisa solo se un tipo case non viene gestito. Non è necessario includere un criterio di eliminazione (_) o var un criterio per trovare una corrispondenza con qualsiasi tipo:
static void PatternMatching()
{
Pet pet = new Dog("Rex");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Se lo stato Null della proprietà dell'unione Value è "forse null", è necessario gestire null anche per evitare un avviso:
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True
var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
Nullabilità
Il compilatore tiene traccia dello stato Null della proprietà di Value un'unione tramite le regole seguenti:
- Quando si crea un valore di unione da un tipo case (tramite un costruttore o una conversione dell'unione),
Valueottiene lo stato Null del valore in ingresso. - Quando il modello di
HasValueaccesso non boxing oTryGetValue(...)i membri eseguono query sul contenuto dell'unione, lo stato Null diValuediventa "non Null" neltrueramo.
Tipi di unione personalizzati
Il compilatore converte una union dichiarazione in una struct dichiarazione. Lo struct è contrassegnato con l'attributo [System.Runtime.CompilerServices.Union] , implementa l'interfaccia IUnion . Include un costruttore pubblico e una conversione implicita per ogni tipo di case insieme a una Value proprietà . Tale modulo generato viene opinioneta. Si tratta sempre di uno struct, riquadri sempre i case di tipo valore e archivia sempre il contenuto come object?.
Quando è necessario un comportamento diverso, ad esempio un'unione basata su classi, una strategia di archiviazione personalizzata, il supporto di interoperabilità o se si vuole adattare un tipo esistente, è possibile creare manualmente un tipo di unione.
Qualsiasi classe o struct con un [Union] attributo è un tipo di unione se segue il modello di unione di base. Il modello di unione di base richiede:
- Attributo
[Union]sul tipo. - Uno o più costruttori pubblici, ognuno con un singolo valore o
inparametro. Il tipo di parametro di ogni costruttore definisce un tipo case. - Proprietà pubblica
Valuedi tipoobject?(oobject) con unagetfunzione di accesso.
Tutti i membri dell'unione devono essere pubblici. Il compilatore usa questi membri per implementare conversioni di unioni, criteri di ricerca ed esaustività. È anche possibile implementare il modello di accesso non boxing o creare un tipo di unione basato su classi.
Il compilatore presuppone che i tipi di unione personalizzati soddisfino queste regole comportamentali:
-
Suono:
Valuerestituiscenullsempre o un valore di uno dei tipi case, mai un valore di un tipo diverso. Per le unioni struct,defaultproduce unValueoggetto dinull. -
Stabilità: se si crea un valore di unione da un tipo di case,
Valuecorrisponde a tale tipo di case (o senulll'input è ).null - Equivalenza della creazione: se un valore è convertibile in modo implicito in due tipi di case diversi, entrambi i membri di creazione producono lo stesso comportamento osservabile.
-
Coerenza dei criteri di accesso: i
HasValuemembri eTryGetValue, se presenti, si comportano in modo equivalente al controlloValuediretto.
L'esempio seguente mostra un tipo di unione personalizzato:
[System.Runtime.CompilerServices.Union]
public struct Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }
public object? Value => _value;
}
public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
static void ManualUnionExample()
{
Shape shape = new Shape(new Circle(5.0));
var area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
};
Console.WriteLine($"{area:F2}"); // output: 78.54
}
Modello di accesso non boxing
Un tipo di unione personalizzato può facoltativamente implementare il modello di accesso non boxing per abilitare l'accesso fortemente tipizzato ai case di tipo valore senza boxing durante la corrispondenza dei criteri. Questo modello richiede:
- Proprietà
HasValuedi tipoboolche restituiscetruequandoValuenonnullè . - Metodo
TryGetValueper ogni tipo di case che restituisceboole recapita il valore tramite unoutparametro.
[System.Runtime.CompilerServices.Union]
public struct IntOrBool : System.Runtime.CompilerServices.IUnion
{
private readonly int _intValue;
private readonly bool _boolValue;
private readonly byte _tag; // 0 = none, 1 = int, 2 = bool
public IntOrBool(int? value)
{
if (value.HasValue)
{
_intValue = value.Value;
_tag = 1;
}
}
public IntOrBool(bool? value)
{
if (value.HasValue)
{
_boolValue = value.Value;
_tag = 2;
}
}
public object? Value => _tag switch
{
1 => _intValue,
2 => _boolValue,
_ => null
};
public bool HasValue => _tag != 0;
public bool TryGetValue(out int value)
{
value = _intValue;
return _tag == 1;
}
public bool TryGetValue(out bool value)
{
value = _boolValue;
return _tag == 2;
}
}
static void NonBoxingExample()
{
IntOrBool val = new IntOrBool((int?)42);
var description = val switch
{
int i => $"int: {i}",
bool b => $"bool: {b}",
};
Console.WriteLine(description); // output: int: 42
}
Il compilatore preferisce TryGetValue la proprietà durante l'implementazione Value dei criteri di ricerca, che evita i tipi valore boxing.
Tipi di unione basati su classi
Una classe può anche essere un tipo di unione. Questo tipo di unione è utile quando è necessaria la semantica di riferimento o l'ereditarietà:
[System.Runtime.CompilerServices.Union]
public class Result<T> : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;
public Result(T? value) { _value = value; }
public Result(Exception? value) { _value = value; }
public object? Value => _value;
}
static void ClassUnionExample()
{
Result<string> ok = new Result<string>("success");
Result<string> err = new Result<string>(new InvalidOperationException("failed"));
Console.WriteLine(Describe(ok)); // output: OK: success
Console.WriteLine(Describe(err)); // output: Error: failed
static string Describe(Result<string> result) => result switch
{
string s => $"OK: {s}",
Exception e => $"Error: {e.Message}",
null => "null",
};
}
Per le unioni basate su classi, il null criterio corrisponde sia a un riferimento Null che a un valore Null Value.
Implementazione dell'unione
L'attributo e l'interfaccia seguenti supportano i tipi di unione in fase di compilazione e runtime:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
Le dichiarazioni di unione generate dal compilatore implementano IUnion. È possibile verificare la presenza di qualsiasi valore di unione in fase di esecuzione usando IUnion:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
Quando si dichiara un union tipo, il compilatore genera uno struct che implementa IUnion. Ad esempio, la Pet dichiarazione (public union Pet(Cat, Dog, Bird);) diventa equivalente a:
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public Pet(Bird value) => Value = value;
public object? Value { get; }
}
Importante
In .NET 11 Preview 2 questi tipi non sono inclusi nel runtime. Per usare i tipi di unione, è necessario dichiararli nel progetto. Verranno incluse in un'anteprima .NET futura.
Specificazione del linguaggio C#
Per altre informazioni, vedere la specifica della funzionalità Unions .