Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Ein Union-Typ stellt einen Wert dar, der einen von mehreren Falltypen sein kann. Unions bieten implizite Konvertierungen aus jedem Falltyp, erschöpfenden Musterabgleich und verbesserte Nullierbarkeitsnachverfolgung. Verwenden Sie das union Schlüsselwort, um einen Union-Typ zu deklarieren:
public union Pet(Cat, Dog, Bird);
Diese Deklaration erstellt eine Pet Vereinigung mit drei Falltypen: Cat, , Dogund Bird. Sie können einer Pet Variablen einen beliebigen Falltypwert zuweisen. Der Compiler stellt sicher, dass switch Ausdrücke alle Falltypen abdecken.
Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie eine erste Dokumentation zu Funktionen in der öffentlichen Vorschau für die kommende Sprachversion.
In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.
Tipp
Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.
Deklarieren Sie eine Union, wenn ein Wert genau einer festen Gruppe von Typen sein muss, und Sie möchten, dass der Compiler erzwingen soll, dass jede Möglichkeit behandelt wird. Zu den häufigen Szenarios gehören:
-
Ergebnis-oder-Fehler-Rückgabe: Eine Methode gibt entweder einen Erfolgswert oder einen Fehlerwert zurück, und der Aufrufer muss beide behandeln. Eine Vereinigung wie
union Result(Success, Error)macht den Satz von Ergebnissen explizit. -
Nachrichten- oder Befehlsversand: Ein System verarbeitet einen geschlossenen Satz von Nachrichtentypen. Eine Union stellt sicher, dass neue Nachrichtentypen bei jedem
switch, der sie noch nicht behandelt, Kompilierungszeitwarnungen erzeugen. - Ersetzen von Markerschnittstellen oder abstrakten Basisklassen: Wenn Sie eine Schnittstelle oder abstrakte Klasse ausschließlich zum Gruppieren von Typen für den Musterabgleich verwenden, erhalten Sie eine Union erschöpfende Überprüfung, ohne dass Vererbung oder freigegebene Member erforderlich sind.
Eine Vereinigung unterscheidet sich von anderen Typdeklarationen auf wichtige Weise:
- Im Gegensatz zu einer
classOder- oderstructUnion definiert eine Union keine neuen Datenmember. Stattdessen werden vorhandene Typen in einen geschlossenen Satz von Alternativen erstellt. - Im Gegensatz zu einer
interfaceUnion wird eine Union geschlossen . Sie definieren die vollständige Liste der Falltypen in der Deklaration, und der Compiler verwendet diese Liste für Erschöpfende Prüfungen. - Im Gegensatz zu einer
recordUnion wird kein Gleichheits-, Klon- oder Dekonstruktionsverhalten hinzugefügt. Eine Vereinigung konzentriert sich auf "welchen Fall ist es?" und nicht auf "welche Felder hat sie?"
Von Bedeutung
In .NET 11 Preview 2 enthält die Laufzeit nicht die UnionAttribute Und-Schnittstelle IUnion . Um Union-Typen zu verwenden, müssen Sie sie selbst deklarieren. Die erforderlichen Erklärungen finden Sie unter "Umsetzung der Union".
Erklärungen der Union
Eine Union-Deklaration gibt einen Namen und eine Liste von Falltypen an:
public union Pet(Cat, Dog, Bird);
Bei Falltypen kann es sich um einen beliebigen Typ handeln, der in Klassen, Strukturen, Schnittstellen, Typparameter, nullable Typen und andere Vereinigungen objectkonvertiert wird. Die folgenden Beispiele zeigen unterschiedliche Falltypmöglichkeiten:
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);
Wenn es sich bei einem Falltyp um einen Werttyp (z int. B. ) handelt, wird der Wert beim Speichern in der Eigenschaft der Union Value boxt. Unions speichern ihre Inhalte als einzelne object? Referenz.
Eine Unionserklärung kann eine Stelle mit zusätzlichen Mitgliedern enthalten, genau wie eine Struktur, die einigen Einschränkungen unterliegt. Union-Deklarationen können keine Instanzfelder, Autoeigenschaften oder Feldähnliche Ereignisse enthalten. Sie können öffentliche Konstruktoren auch nicht mit einem einzelnen Parameter deklarieren, da der Compiler diese Konstruktoren als Union Creation-Member generiert:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
Umwandlungen der Union
Eine implizite Union-Konvertierung ist von jedem Falltyp in den Union-Typ vorhanden. Sie müssen keinen Konstruktor explizit aufrufen:
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 }
}
Union-Konvertierungen funktionieren durch Aufrufen des entsprechenden generierten Konstruktors. Wenn ein benutzerdefinierter impliziter Konvertierungsoperator für denselben Typ vorhanden ist, hat der benutzerdefinierte Operator Vorrang vor der Union-Konvertierung. Ausführliche Informationen zur Konvertierungspriorität finden Sie in der Sprachspezifikation.
Eine Union-Konvertierung in eine unionable Union-Struktur (T?) funktioniert auch, wenn T es sich um einen Unionstyp handelt:
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",
};
}
Union-Abgleich
Wenn Sie eine Übereinstimmung mit einem Union-Typ erstellen, gelten Muster für das Eigentum der Union Value , nicht für den Unionswert selbst. Dieses "Entwrapping"-Verhalten bedeutet, dass die Vereinigung transparent für den Musterabgleich ist:
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
}
Zwei Muster sind Ausnahmen von dieser Regel: das var Muster und das Verwerfenmuster _ gelten für den Unionswert selbst, nicht seine Value Eigenschaft. Wird var verwendet, um den Unionswert zu erfassen, wenn GetPet() ein Pet? (Nullable<Pet>) zurückgegeben wird:
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
In logischen Mustern folgt jeder Verzweigung der entwrapping-Regel einzeln. Das folgende Muster testet, dass der Pet? Wert nicht null ist undValue nicht null ist:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}
Hinweis
Da Muster angewendet werden Value, stimmt ein Muster wie in pet is Pet der Regel nicht überein, da es Pet auf den Inhalt der Vereinigung und nicht auf die Vereinigung selbst getestet wird.
Nullabgleich
Bei Strukturgewerkschaften überprüft das null Muster, ob Value null ist:
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
}
Bei klassenbasierten Vereinigungen null ist der Union-Verweis entweder null oder seine Value Eigenschaft 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 */ }
Bei nullablen Union-Strukturtypen (Pet?) wird erfolgreich ausgeführt, null wenn der nullfähige Wrapper keinen Wert aufweist oder wenn die zugrunde liegende Union Value null ist.
Erschöpfende Union
Ein switch Ausdruck ist erschöpfend, wenn er alle Falltypen einer Union behandelt. Der Compiler warnt nur, wenn ein Falltyp nicht behandelt wird. Sie müssen kein Verwerfenmuster (_) oder var ein Muster einschließen, um einem beliebigen Typ zu entsprechen:
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
}
Wenn der NULL-Zustand der Eigenschaft der Union Value "vielleicht null" ist, müssen Sie auch behandeln null , um eine Warnung zu vermeiden:
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
}
NULL-Zulässigkeit
Der Compiler verfolgt den NULL-Zustand der Eigenschaft einer Union Value über die folgenden Regeln nach:
- Wenn Sie einen Union-Wert aus einem Falltyp (über einen Konstruktor oder eine Union-Konvertierung) erstellen,
Valueruft den NULL-Status des eingehenden Werts ab. - Wenn die Elemente oder Elemente des Nicht-Boxing-Zugriffsmusters
HasValueden Inhalt der Union abfragen, wird der NULL-ZustandValue"nicht null" für dietrueVerzweigung.TryGetValue(...)
Benutzerdefinierte Union-Typen
Der Compiler konvertiert eine union Deklaration in eine struct Deklaration. Die Struktur ist mit dem [System.Runtime.CompilerServices.Union] Attribut gekennzeichnet und implementiert die IUnion Schnittstelle. Sie enthält einen öffentlichen Konstruktor und eine implizite Konvertierung für jeden Falltyp zusammen mit einer Value Eigenschaft. Diese generierte Form wird meinungsiert. Es ist immer eine Struktur, immer Felder Werttypfälle, und speichert immer Inhalte als object?.
Wenn Sie ein anderes Verhalten benötigen – z. B. eine klassenbasierte Union, eine benutzerdefinierte Speicherstrategie, interop-Unterstützung oder wenn Sie einen vorhandenen Typ anpassen möchten – können Sie einen Union-Typ manuell erstellen.
Jede Klasse oder Struktur mit einem [Union] Attribut ist ein Union-Typ , wenn sie dem grundlegenden Union-Muster folgt. Das grundlegende Union-Muster erfordert Folgendes:
- Ein
[Union]Attribut für den Typ. - Mindestens ein öffentlicher Konstruktor mit einem einzelnen Nachwert oder
inParameter. Der Parametertyp jedes Konstruktors definiert einen Falltyp. - Eine öffentliche
ValueEigenschaft vom Typobject?(oderobject) mit einemgetAccessor.
Alle Gewerkschaftsmitglieder müssen öffentlich sein. Der Compiler verwendet diese Member zum Implementieren von Union-Konvertierungen, Musterabgleichs- und Erschöpfenheitsprüfungen. Sie können auch das Zugriffsmuster ohne Boxen implementieren oder einen klassenbasierten Union-Typ erstellen.
Der Compiler geht davon aus, dass benutzerdefinierte Union-Typen diese Verhaltensregeln erfüllen:
-
Soundness:
ValueGibtnullimmer zurück oder einen Wert eines der Falltypen – niemals einen Wert eines anderen Typs. Für strukturgeflechtliche Vereinigungen entstehtdefaulteineValuevonnull. -
Stabilität: Wenn Sie einen Union-Wert aus einem Falltyp erstellen,
Valueentspricht dieser Fallart (oder istnulldie Eingabenull). - Äquivalenz beim Erstellen: Wenn ein Wert implizit in zwei verschiedene Falltypen konvertierbar ist, erzeugen beide Erstellungsmember dasselbe feststellbare Verhalten.
-
Zugriffsmusterkonsistenz: Wenn vorhanden, verhalten sich die
HasValueTryGetValueElemente gleichbedeutend mit der direkten ÜberprüfungValue.
Das folgende Beispiel zeigt einen benutzerdefinierten Union-Typ:
[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
}
Zugriffsmuster ohne Boxen
Ein benutzerdefinierter Union-Typ kann optional das Zugriffsmuster ohne Boxing implementieren, um den stark typierten Zugriff auf Werttypfälle ohne Boxen während des Musterabgleichs zu ermöglichen. Dieses Muster erfordert Folgendes:
- Eine
HasValueEigenschaft vom Typ, die zurückgegebentruewird, wennValuedies nichtnullder Wertboolist. - Eine
TryGetValueMethode für jeden Falltyp, der den Wert über einenoutParameter zurückgibtboolund liefert.
[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
}
Der Compiler bevorzugt TryGetValue die Value Eigenschaft beim Implementieren des Musterabgleichs, wodurch Boxwerttypen vermieden werden.
Klassenbasierte Union-Typen
Eine Klasse kann auch ein Union-Typ sein. Diese Art von Vereinigung ist nützlich, wenn Sie Referenzsemantik oder Vererbung benötigen:
[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",
};
}
Bei klassenbasierten Vereinigungen entspricht das null Muster sowohl einem Nullverweis als auch einem Nullwert Value.
Umsetzung der Union
Das folgende Attribut und die Schnittstelle unterstützen Union-Typen zur Kompilierungszeit und Laufzeit:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
Union-Deklarationen, die vom Compiler IUniongeneriert werden. Sie können zur Laufzeit auf einen beliebigen Unionswert überprüfen, indem Sie Folgendes verwenden IUnion:
if (value is IUnion { Value: null }) { /* the union's value is null */ }
Wenn Sie einen union Typ deklarieren, generiert der Compiler eine Struktur, die implementiert IUnionwird. Die Deklaration (public union Pet(Cat, Dog, Bird);) wird z. BPet. gleichbedeutend mit:
[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; }
}
Von Bedeutung
In .NET 11 Preview 2 sind diese Typen nicht in der Laufzeit enthalten. Um Union-Typen zu verwenden, müssen Sie sie in Ihrem Projekt deklarieren. Sie werden in einer zukünftigen .NET-Vorschau enthalten sein.
C#-Sprachspezifikation
Weitere Informationen finden Sie in der Unions-Featurespezifikation .