Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
En unionstyp representerar ett värde som kan vara en av flera skiftlägestyper. Unioner tillhandahåller implicita konverteringar från varje ärendetyp, fullständig mönstermatchning och förbättrad nullability-spårning. Använd nyckelordet union för att deklarera en unionstyp:
public union Pet(Cat, Dog, Bird);
Den här deklarationen skapar en Pet union med tre falltyper: Cat, Dogoch Bird. Du kan tilldela valfritt skiftlägestypvärde till en Pet variabel. Kompilatorn ser till att switch uttrycken täcker alla skiftlägestyper.
C#-språkreferensen dokumenterar den senaste versionen av C#-språket. Den innehåller även inledande dokumentation för funktioner i offentliga förhandsversioner för den kommande språkversionen.
Dokumentationen identifierar alla funktioner som först introducerades i de tre senaste versionerna av språket eller i aktuella offentliga förhandsversioner.
Tips/Råd
Information om när en funktion först introducerades i C# finns i artikeln om språkversionshistoriken för C#.
Deklarera en union när ett värde måste vara exakt en av en fast uppsättning typer och du vill att kompilatorn ska framtvinga att alla möjligheter hanteras. Vanliga scenarier är:
-
Resultat-eller-fel returnerar: En metod returnerar antingen ett lyckat värde eller ett felvärde och anroparen måste hantera båda. En union som
union Result(Success, Error)gör uppsättningen resultat explicita. -
Meddelande- eller kommandosändning: Ett system bearbetar en sluten uppsättning meddelandetyper. En union säkerställer att nya meddelandetyper skapar kompileringstidsvarningar vid varje
switchsom inte hanterar dem ännu. - Ersätta markörgränssnitt eller abstrakta basklasser: Om du använder ett gränssnitt eller en abstrakt klass enbart för att gruppera typer för mönstermatchning ger en union fullständig kontroll utan att kräva arv eller delade medlemmar.
En union skiljer sig från andra typer av deklarationer på viktiga sätt:
- Till skillnad från en
classellerstructdefinierar inte en union nya datamedlemmar. I stället består befintliga typer i en sluten uppsättning alternativ. - Till skillnad från en
interface, stängs en union – du definierar den fullständiga listan över ärendetyper i deklarationen, och kompilatorn använder den listan för fullständighetskontroller. - Till skillnad från en
recordlägger en union inte till likhets-, klonings- eller dekonstruktionsbeteende. En union fokuserar på "vilket fall är det?" snarare än "vilka fält har den?"
Viktigt!
I .NET 11 Preview 2 innehåller UnionAttribute körningen inte gränssnittet och IUnion . Om du vill använda unionstyper måste du deklarera dem själv. Information om vilka förklaringar som krävs finns i Unionsgenomförande.
Unionsdeklarationer
En unionsdeklaration anger ett namn och en lista över skiftlägestyper:
public union Pet(Cat, Dog, Bird);
Skiftlägestyper kan vara valfri typ som konverteras till object, inklusive klasser, structs, gränssnitt, typparametrar, nullbara typer och andra fackföreningar. I följande exempel visas olika möjligheter för skiftlägestyper:
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);
När en ärendetyp är en värdetyp (till exempel int) boxas värdet när det lagras i fackets Value egenskap. Facken lagrar sitt innehåll som en enda object? referens.
En facklig förklaring kan innehålla ett organ med ytterligare medlemmar, precis som en struct, som omfattas av vissa begränsningar. Unionsdeklarationer kan inte innehålla instansfält, automatiska egenskaper eller fältliknande händelser. Du kan inte heller deklarera offentliga konstruktorer med en enda parameter eftersom kompilatorn genererar dessa konstruktorer som medlemmar i skapande av union:
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
Unionskonverteringar
Det finns en implicit unionskonvertering från varje ärendetyp till unionstypen. Du behöver inte anropa en konstruktor explicit:
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-konverteringar fungerar genom att anropa motsvarande genererade konstruktor. Om det finns en användardefinierad implicit konverteringsoperator för samma typ prioriteras den användardefinierade operatorn framför unionskonverteringen. Mer information om konverteringsprioritet finns i språkspecifikationen.
En unionskonvertering till en nullbar unionstruct (T?) fungerar också när T är en unionstyp:
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-matchning
När du mönstermatchar på en unionstyp gäller mönster för fackets Value egenskap, inte själva unionsvärdet. Det här "avskrivningsbeteendet" innebär att unionen är transparent för mönstermatchning:
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
}
Två mönster är undantag från den här regeln: var mönstret och mönstret för ignorerande _ gäller för själva unionsvärdet, inte dess Value egenskap. Använd var för att avbilda union-värdet när GetPet() returnerar en Pet? (Nullable<Pet>):
if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }
I logiska mönster följer varje gren avskrivningsregeln individuellt. Följande mönster testar att Pet? inte är null och att det Value inte är null:
GetPet() switch
{
var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}
Anmärkning
Eftersom mönster gäller för Valuematchar inte ett mönster som pet is Pet vanligtvis, eftersom Pet det testas mot innehållet i unionen, inte själva unionen.
Null-matchning
För struct-fackföreningar null kontrollerar mönstret om Value är 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
}
För klassbaserade fackföreningar null lyckas när antingen själva unionsreferensen är null eller dess Value egenskap är 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 */ }
För nullable union struct types (Pet?) null lyckas när den nullbara omslutningen inte har något värde eller när den underliggande unionen är Value null.
Unionens fullständighet
Ett switch uttryck är uttömmande när det hanterar alla falltyper av en union. Kompilatorn varnar bara om en ärendetyp inte hanteras. Du behöver inte inkludera ett ignorerande mönster (_) eller var mönster för att matcha någon typ:
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
}
Om null-tillståndet för unionens Value egenskap är "kanske null" måste du också hantera null för att undvika en varning:
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
}
Nullbarhet
Kompilatorn spårar null-tillståndet för en unions Value egenskap genom följande regler:
- När du skapar ett union-värde från en ärendetyp (via en konstruktor eller unionkonvertering)
Valuefår du null-tillståndet för det inkommande värdet. - När icke-boxningsåtkomstmönstrets
HasValueellerTryGetValue(...)medlemmarna frågar fackets innehåll blir null-tillståndetValueför "inte null" på grenentrue.
Anpassade unionstyper
Kompilatorn konverterar en union deklaration till en struct deklaration. Structen är markerad med [System.Runtime.CompilerServices.Union] attributet och implementerar IUnion gränssnittet. Den innehåller en offentlig konstruktor och en implicit konvertering för varje ärendetyp tillsammans med en Value egenskap. Det genererade formuläret är åsiktsutlåtande. Det är alltid en struct, rutor alltid värdetypsfall och lagrar alltid innehåll som object?.
När du behöver ett annat beteende – till exempel en klassbaserad union, en anpassad lagringsstrategi, interop-stöd eller om du vill anpassa en befintlig typ – kan du skapa en unionstyp manuellt.
En klass eller struct med ett [Union] attribut är en unionstyp om den följer det grundläggande unionsmönstret. Det grundläggande unionsmönstret kräver:
- Ett
[Union]attribut för typen. - En eller flera offentliga konstruktorer, var och en med ett enda värde eller
inen parameter. Parametertypen för varje konstruktor definierar en skiftlägestyp. - En offentlig
Valueegenskap av typenobject?(ellerobject) med engetaccessor.
Alla fackföreningsmedlemmar måste vara offentliga. Kompilatorn använder dessa medlemmar för att implementera fackliga konverteringar, mönstermatchning och fullständighetskontroller. Du kan också implementera åtkomstmönstret för icke-boxning eller skapa en klassbaserad unionstyp.
Kompilatorn förutsätter att anpassade unionstyper uppfyller dessa beteenderegler:
-
Ljud:
Valuereturnerarnullalltid eller ett värde för en av skiftlägestyperna – aldrig ett värde av en annan typ. För struct-fackföreningar producerardefaultenValueavnull. -
Stabilitet: Om du skapar ett union-värde från en ärendetyp
Valuematchar den ärendetypen (eller omnullindata varnull). - Ekvivalens för skapande: Om ett värde implicit kan konverteras till två olika skiftlägestyper skapar båda medlemmarna samma observerbara beteende.
-
Konsekvens för åtkomstmönster: Medlemmarna
HasValueochTryGetValue, om de finns, beter sig på samma sätt som kontrollValuedirekt.
I följande exempel visas en anpassad unionstyp:
[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
}
Åtkomstmönster som inte är boxning
En anpassad unionstyp kan om du vill implementera åtkomstmönstret för icke-boxning för att ge starkt skrivskyddad åtkomst till värdetypsfall utan boxning under mönstermatchning. Det här mönstret kräver:
- En
HasValueegenskap av typenboolsom returnerartruenärValueintenullär . - En
TryGetValuemetod för varje skiftlägestyp som returnerarbooloch levererar värdet via enoutparameter.
[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
}
Kompilatorn föredrar TryGetValue framför Value egenskapen vid implementering av mönstermatchning, vilket undviker boxningsvärdetyper.
Klassbaserade unionstyper
En klass kan också vara en unionstyp. Den här typen av union är användbar när du behöver referenssemantik eller arv:
[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",
};
}
För klassbaserade unioner null matchar mönstret både en null-referens och en null Value.
Unionsimplementering
Följande attribut och gränssnitt stöder unionstyper vid kompileringstid och körning:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
Unionsdeklarationer som genereras av kompilatorn implementerar IUnion. Du kan söka efter valfritt unionsvärde vid körning med hjälp IUnionav :
if (value is IUnion { Value: null }) { /* the union's value is null */ }
När du deklarerar en union typ genererar kompilatorn en struct som implementerar IUnion. Deklarationen Pet (public union Pet(Cat, Dog, Bird);) blir till exempel likvärdig med:
[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; }
}
Viktigt!
I .NET 11 Preview 2 ingår inte dessa typer i körningen. Om du vill använda unionstyper måste du deklarera dem i projektet. De kommer att ingå i en framtida .NET-förhandsversion.
Språkspecifikation för C#
Mer information finns i Unions funktionsspecifikation .