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?"
Union-Typen basieren auf den Namespace und UnionAttribute den IUnion TypenSystem.Runtime.CompilerServices. Die .NET 11 Laufzeit enthält diese Typen.
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, string Breed);
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, schachtelt eine Union-Deklaration den Wert und speichert ihn als einzelnen object? Bezug.
Benutzerdefinierte Union-Typen können eine andere Speicherstrategie verwenden.
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. Jeder Konstruktor, den Sie deklarieren, muss einen this(...) Initialisierer verwenden, um direkt oder indirekt einen generierten Konstruktor zu delegieren. 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", "Labrador");
Console.WriteLine(pet.Value); // output: Dog { Name = Rex, Breed = Labrador }
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", "Beagle");
Pet? noPet = null;
Console.WriteLine(Describe(maybePet)); // output: 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 ein Typ- oder Deklarationsmuster für einen Union-Ausdruck verwenden, testet das Muster den Inhalt der Union, nicht die Union-Instanz. Ein Muster testet ValueDogz. B. folgendesDog:
static void PatternMatching()
{
Pet pet = new Dog("Rex", "Labrador");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Das Formular des Musters bestimmt, welchen Wert es testet:
| Pattern | Verhalten für einen Union-Ausdruck |
|---|---|
Typ- oder Deklarationsmuster, z Dog . B. oder Dog dog |
Testet den Inhalt anhand des angegebenen Typs. |
Eigenschafts- oder Positionsmuster mit einem Typ, z. B. Dog { Breed: "Labrador" } |
Testet den Inhalt anhand des Typs, wendet dann die geschachtelten Muster auf diesen Fallwert an. |
Eigenschafts- oder Positionsmuster ohne Typ, z. B. { Name: "Rex" } |
Verwendet die Eigenschaften oder Deconstruct Methoden der Union-Instanz. |
| Listenmuster | Verwendet die Anzahl und Indizierung der Mitglieder der Union-Instanz, nicht die der zugehörigen Inhalte. |
var oder Verwerfen _ des Musters |
Erfasst oder stimmt mit der eingehenden Union-Instanz überein, ohne sie zu entpacken. |
| Konstantes oder relationales Muster | Testet den Inhalt. Das null Muster behandelt auch einen NULL-Union-Verweis oder einen nullfähigen Wrapper. |
Definieren Sie z. B. eine Pet Union mit einer schreibgeschützten Name Eigenschaft, die an das enthaltene Element Cat delegiert wird.Dog Die Typmuster im Ausdruck der Eigenschaft switch testen den Inhalt:
public union Pet(Cat, Dog)
{
public string Name => this switch
{
Cat cat => cat.Name,
Dog dog => dog.Name,
null => "",
};
}
Das typierte Muster Dog { Breed: "Labrador" } testet den Inhalt und liest die Hunde Breed, eine Eigenschaft, die die Vereinigung nicht hat. Das untypisierte Muster { Name: "Rex" } liest stattdessen die Eigenschaft der Union Name , sodass es auch mit einer Katze namens Rex übereinstimmt:
Pet pet = new Dog("Rex", "Labrador");
Console.WriteLine(pet is Dog { Breed: "Labrador" }); // output: True
Console.WriteLine(pet is { Name: "Rex" }); // output: True
pet = new Cat("Rex");
Console.WriteLine(pet is Dog { Breed: "Labrador" }); // output: False
Console.WriteLine(pet is { Name: "Rex" }); // output: True
Das not Muster negiert sein geschachteltes Muster. In pet is not null, das geschachtelte null Muster überprüft den Inhalt, so not null ist ein nützlicher Has-a-Wert-Test. Im Gegensatz dazu überprüft ein leeres, nicht typisiertes Eigenschaftsmuster { }die Union-Instanz. Sie entspricht immer einer nicht nullierbaren Strukturunion, auch wenn Value null ist:
Pet empty = default;
Console.WriteLine(empty is { }); // output: True
Console.WriteLine(empty is not null); // output: False
if (empty is var captured)
{
Console.WriteLine(captured.Value is null); // output: True
}
Logische Muster bewahren diese Unterscheidungen auf. Erfasst var pet and not nullin , var pet erfasst die eingehende Union und not null testet, ob er nicht NULL-Inhalte enthält. In not null and var pet, das var Muster erfasst weiterhin die eingehende Union: not ändert nicht den Wert, der an das rechte Muster übergeben wird. Im Gegensatz dazu Dog and { Breed: "Labrador" }übergibt das Typmuster das übereinstimmende Dog Eigenschaftsmuster.
Hinweis
Ein Typmuster muss mit mindestens einem Falltyp kompatibel sein. Für die Pet Deklarationen in diesem Artikel pet is Pet erzeugt Compilerfehler CS8121: Keiner der Falltypen kann übereinstimmen Pet. Es handelt sich nicht um einen Laufzeittest der Union-Instanz. Um nach Inhalten zu suchen, zu verwenden pet is not null oder pet.Value is not null; um die Vereinigung selbst zu erfassen, verwenden Sie ein var Muster.
Kurz gesagt testen Typ- und Deklarationsmuster den Inhalt. Rekursive Muster mit einem Typ testen auch den Inhalt; rekursive Muster ohne Typ verwenden die eigenen Mitglieder der Union. Diese Regeln sind in einem is Ausdruck und einem switch Ausdruck identisch.
Nullabgleich
Bei Strukturgewerkschaften überprüft das null Muster, ob Value null ist:
static void NullHandling()
{
Pet pet = default;
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
Console.WriteLine(pet.Value is null); // output: True
}
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 vollständig über die Falltypen einer Union, wenn er jeden Falltyp behandelt. Wenn die Nullierbarkeitsanalyse auch den Inhalt berücksichtigt, der nicht null ist, benötigen Sie keinen Verwerfen (_) oder var Arm:
static void PatternMatching()
{
Pet pet = new Dog("Rex", "Labrador");
var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
Um Nullinhalte zu behandeln, bevorzugen Sie einen null Arm über einem var oder verwerfen (_) Arm. Das null Muster gibt Ihre Absicht an und behält Warnungen bei, wenn Sie einen neuen Falltyp ohne passenden Arm hinzufügen. Ein var oder verwerfen Arm akzeptiert auch jeden neuen Fall, sodass der Compiler Sie nicht vor diesem fehlenden Fall warnen kann.
Wenn der nachverfolgte NULL-Zustand Value "vielleicht null" ist, warnt der Compiler auch, wenn Sie einen null Arm weglassen. Hat z. B NullablePet . einen Null-Groß-/Kleinschreibungstyp, sodass der Inhalt des Parameters null sein kann:
public union NullablePet(Cat?, Dog, Bird);
static string DescribeNullableCase(NullablePet pet) => pet switch
{
Cat cat => cat.Name,
Dog dog => dog.Name,
Bird bird => bird.Name,
null => "no pet",
};
Wenn Sie den Arm von diesem Schalter entfernen, wird die null Warnung CS8655 erzeugt.
Eine nullfähige Analyse garantiert nicht, dass eine generierte Union zur Laufzeit Nicht-Null-Inhalte enthält. Eine Standardunion enthält NULL, einschließlich eines standardmäßig initialisierten Felds oder Arrayelements. Wenn alle Falltypen nicht ullierbar sind, ist der Standardmäßige Nachverfolgungszustand Value trotzdem "nicht null", sodass der Compiler nicht unbedingt vor einem ausgelassenen null Arm warnt. Behandeln Sie null die Korrektheit, wenn standardinhalte Ihren Switch erreichen können:
static void NullHandling()
{
Pet pet = default;
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
Console.WriteLine(pet.Value is null); // output: True
}
In diesem Pet Beispiel erzeugt das Entfernen des null Arms keine Warnung ohne null-Arm, es sei denn, früherer Code ändert den Überarbeitungszustand null, der Schalter wird jedoch ausgelöst, wenn er den Standardwert empfängt SwitchExpressionException . Ein früherer is null Test kann den nachverfolgten Zustand ändern und eine Warnung auslösen. Dieses Verhalten unterscheidet sich von der eindeutigen Zuordnung: Der Compiler lehnt das Lesen einer nicht zugewiesenen lokalen Variablen ab.
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 für jeden Falltyp und eine Value Eigenschaft. Die Sprache stellt implizite Union-Konvertierungen bereit, die diese Konstruktoren aufrufen. 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 weitere Mitglieder hinzufügen.
Der Compiler geht davon aus, dass benutzerdefinierte Union-Typen diese Verhaltensregeln erfüllen:
-
Soundness:
ValueGibtnullimmer einen Wert eines der Falltypen zurück, einschließlich eines Standardstrukturwerts. Eine benutzerdefinierte Union kann ihren Standardwert als Nicht-Null-Groß-/Kleinschreibung darstellen, im Gegensatz zum Null-Inhalt einer standardgenerierten Union. -
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
}
Für den Musterabgleich ruft TryGetValue der Compiler Typmusterprüfungen (z union is T. B. ) und HasValue für Nullmusterprüfungen (z union is null. B. ) auf, um boxen für Werttypfälle zu vermeiden. Jedes Element bezieht sich auf eine eigene Musterart – keines ist ein Fallback für die andere. Wenn ein Element fehlt, überprüft der Compiler stattdessen die Value Eigenschaft für dieses Muster. Weitere Informationen finden Sie unter How the compiler generates code for pattern matching.
Union-Mitgliedsanbieter
Ein Union-Typ kann seine Union-Mitglieder an eine öffentliche geschachtelte IUnionMembers Schnittstelle delegieren, die sie direkt enthält und implementiert. Diese Schnittstelle ist der Union-Mitgliedsanbieter. Der Compiler findet Union-Member nur auf der Anbieterschnittstelle, einschließlich geerbter Member. Mitglieder, die nur für den Unionstyp selbst deklariert wurden, werden nicht als Gewerkschaftsmitglieder verwendet.
Bei Verwendung eines Union-Mitgliedsanbieters muss die Schnittstelle die folgenden erforderlichen Member deklarieren:
-
Statische
CreateMethoden: Eine für jeden Falltyp mit einem einzelnen Parameter und einer Rückgabetypidentität, die in den Union-Typ umsetzbar ist. Diese Methoden richten Ihre Falltypen ein. -
Value-Eigenschaft: Eine öffentlicheobjectoderobject?Eigenschaft mit einemgetAccessor, der den enthaltenen Wert zurückgibt.
Sie können diese Member optional auf der Schnittstelle deklarieren, damit der Compiler effizienteren Musterabgleichscode generiert:
-
TryGetValuemethods: One for each case type, returningboolwith anoutparameter of the case type. -
HasValue-Eigenschaft: Eine öffentlicheboolEigenschaft mit einemgetAccessor, der nicht zurückgegebentrueValuewirdnull.
Wie der Compiler Code für den Musterabgleich generiert: TryGetValue und HasValue auf verschiedene Arten von Mustern anwenden – kein Fallback für die andere:
- Bei einem Typmuster, z
union is T. B. , ruft der Compiler bei der Deklaration aufTryGetValue(out T value), extrahiert den Wert und wendet das Muster ohne Boxen darauf an. Andernfalls wendet der Compiler das Muster auf dieValueEigenschaft an, die eine LaufzeittypüberprüfungTdurchführt und den Wert beiTeinem Werttyp boxen kann. - Bei einem NULL-Muster, z
union is null. B. , ruftHasValueder Compiler auf, wenn er deklariert wird, um zu testen, ob die Union einen Wert enthält. Andernfalls wendet der Compiler das Nullmuster direkt auf dieValueEigenschaft an.
Jedes Element ist unabhängig voneinander optional: ohne TryGetValueTypmuster verwenden Value; ohne HasValueNULL-Muster wird verwendet Value. Durch das Deklarieren eines der Member kann der Compiler effizienteren, stark typierten Code für diese Musterart generieren:
[System.Runtime.CompilerServices.Union]
public struct 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; }
// Optional but recommended: TryGetValue enables efficient pattern matching
bool TryGetValue(out T value);
bool TryGetValue(out Exception value);
}
object? IUnionMembers.Value => _value;
public bool TryGetValue(out T value)
{
if (_value is T t)
{
value = t;
return true;
}
value = default!;
return false;
}
public bool TryGetValue(out Exception value)
{
if (_value is Exception e)
{
value = e;
return true;
}
value = default!;
return false;
}
}
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.
C#-Sprachspezifikation
Weitere Informationen finden Sie in der Unions-Featurespezifikation .