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?"
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. Die folgende Length Union fügt eine TotalMeters Eigenschaft hinzu, die musterabgleich verwendet, um jeden Falltyp zu behandeln, zusammen mit einer Add Methode, die zwei Längen kombiniert:
public record class Meters(double Value);
public record class Feet(double Value);
public union Length(Meters, Feet)
{
public double TotalMeters => this switch
{
Meters m => m.Value,
Feet f => f.Value * 0.3048,
_ => throw new InvalidOperationException("The Length has no value."),
};
public Length Add(Length other) => new Meters(TotalMeters + other.TotalMeters);
}
Umwandlungen der Union
Eine implizite Union-Konvertierung ist von jedem Falltyp in den Union-Typ vorhanden:
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. Wenn mehr als ein Falltyp gleichermaßen auf den Quellwert anwendbar ist, ist die Union-Konvertierung mehrdeutig, und der Compiler meldet einen Fehler. Ausführliche Informationen zur Konvertierungspriorität finden Sie in der Featurespezifikation.
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-Musterabgleich
Wenn Sie eine Mustervergleichung für einen Union-Typ ausführen, gelten Muster in der Regel 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
}
Drei Muster sind Ausnahmen von dieser Regel: das Verwerfenmuster _ , das var Muster und das not Muster gelten für den Unionswert selbst und nicht für 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. Die linke Verzweigung eines and Musters kann den eingehenden Wert ändern, den die rechte Verzweigung sieht. Da das not Muster nicht auf den Wert der eingehenden Union Valueangewendet wird, wird der Wert für die folgende Verzweigung durch einen führenden not null Wert nicht entpackt:
GetPet() switch
{
// 'var pet' captures the Pet?; 'not null' applies to the Pet? value (not pet.Value)
var pet and not null => ...,
// 'not null' doesn't unwrap to Pet, so 'var value' still captures the Pet?
not null and var 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, das einem beliebigen Typ entspricht, wenn der Ausdruck definitiv zugewiesen 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
}
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
}
Diese Situation kann auftreten, wenn der union Ausdruck der Standardwert ist oder nicht definitiv zugewiesen ist, wie im vorherigen Beispiel gezeigt.
NULL-Zulässigkeit
Der Compiler verfolgt den NULL-Zustand der Eigenschaft einer Union Value über die folgenden Regeln nach:
- Der Standard-NULL-Zustand einer Union-Eigenschaft
Valuelautet "vielleicht null", wenn der Standard-Null-Zustand eines falltyps "vielleicht null" lautet. Andernfalls lautet der Standard-NULL-Zustand "nicht NULL". - 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-ZustandTryGetValue(...)"nicht null" für dieValueVerzweigung.true
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?.
Möglicherweise benötigen Sie ein anderes Verhalten, wenn Sie einen vorhandenen Typ anpassen, eine klassenbasierte Union erstellen oder eine benutzerdefinierte Speicherstrategie verwenden möchten oder wenn Sie Interopunterstützung benötigen. Sie können 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 vorherigen 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. Ihr benutzerdefinierter Union-Typ kann zusätzliche Mitglieder hinzufügen.
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ückgegebenboolwird, wenntruedies nichtValueder Wertnullist. - Eine
TryGetValueMethode für jeden Falltyp, der den Wert über einenboolParameter zurückgibtoutund liefert.TryGetValuegibt nur zurücktrue, wennValuees sich um einen Wert ungleich NULL des Falltyps handelt. DeroutTyp des Parameters ist identitätsverwendbar für den Falltyp oder für den zugrunde liegenden Werttyp, wenn der Groß-/Kleinschreibungstyp ein Nullwerttyp ist.
[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.
Union-Mitgliedsanbieter
Ein Union-Typ kann seine Union-Mitglieder an eine geschachtelte IUnionMembers Schnittstelle delegieren. Wenn diese Schnittstelle vorhanden ist, sucht der Compiler anstelle von Konstruktoren nach Create Factorymethoden:
[System.Runtime.CompilerServices.Union]
public record class Outcome<T> : Outcome<T>.IUnionMembers
{
private readonly object? _value;
private Outcome(object? value) => _value = value;
public interface IUnionMembers
{
static Outcome<T> Create(T? value) => new(value);
static Outcome<T> Create(Exception? value) => new(value);
object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
Union-Mitgliedsanbieter sind nützlich, wenn der Union-Typ einen privaten Konstruktor benötigt oder wenn für die Erstellungslogik ein Factorymuster erforderlich ist, z. B. mit record class Union-Typen.
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
Union-Typen basieren auf den Namespace und IUnion den UnionAttribute TypenSystem.Runtime.CompilerServices. Die Laufzeit enthält diese Typen, die mit .NET 11 Preview 5 beginnen:
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 (Pet) wird z. Bpublic union Pet(Cat, Dog, Bird);. 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; }
}
C#-Sprachspezifikation
Weitere Informationen finden Sie in der Unions-Featurespezifikation .