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?"
Union-typer förlitar sig på typerna UnionAttribute och IUnion i System.Runtime.CompilerServices namnområdet. .NET 11-körningen innehåller dessa typer.
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, 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);
När en ärendetyp är en värdetyp (t.ex int. ) rutor en unionsdeklaration värdet och lagrar det som en enda object? referens.
Anpassade unionstyper kan använda en annan lagringsstrategi.
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 unionen. Alla konstruktorer som du deklarerar måste använda en this(...) initierare för att direkt eller indirekt delegera till en genererad konstruktor. Följande Length union lägger till en TotalMeters egenskap som använder mönstermatchning för att hantera varje skiftlägestyp, tillsammans med en Add metod som kombinerar två längder:
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);
}
Unionskonverteringar
Det finns en implicit unionskonvertering från varje ärendetyp till unionstypen:
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-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. Om mer än en falltyp är lika tillämplig för källvärdet är unionskonverteringen tvetydig och kompilatorn rapporterar ett fel. Mer information om konverteringsprioritet finns i funktionsspecifikationen.
En unionskonvertering till en nullbar unionstruct (T?) fungerar också när T är en unionstyp:
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",
};
}
Matchning av unionsmönster
När du använder ett typ- eller deklarationsmönster i ett unionsuttryck testar mönstret fackets innehåll, inte unionsinstansen. Ett mönster testar till exempel Dog om Value är en Dog:
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
}
Mönstrets formulär avgör vilket värde det testar:
| Pattern | Beteende för ett unionsuttryck |
|---|---|
Typ- eller deklarationsmönster, till exempel Dog eller Dog dog |
Testar innehållet mot den angivna typen. |
Egenskap eller positionsmönster med en typ, till exempel Dog { Breed: "Labrador" } |
Testar innehållet mot typen och tillämpar sedan de kapslade mönstren på det skiftlägesvärdet. |
Egenskap eller positionsmönster utan typ, till exempel { Name: "Rex" } |
Använder unionsinstansens egenskaper eller Deconstruct metod. |
| Listmönster | Använder den fackliga instansens antal och indexeringsmedlemmar, inte medlemmar i dess innehåll. |
var eller ignorera _ mönster |
Samlar in eller matchar den inkommande unionsinstansen utan att packa upp den. |
| Konstant eller relationsmönster | Testar innehållet. Mönstret null hanterar även en null-unionsreferens eller null-omslutning. |
Definiera till exempel en Pet union med en skrivskyddad Name egenskap som delegerar till den inneslutna Cat eller Dog. Typmönstren i egenskapens switch uttryck testar innehållet:
public union Pet(Cat, Dog)
{
public string Name => this switch
{
Cat cat => cat.Name,
Dog dog => dog.Name,
null => "",
};
}
Det inskrivna mönstret Dog { Breed: "Labrador" } testar innehållet och läser hundens Breed, en egenskap som facket inte har. Det otypade mönstret { Name: "Rex" } läser fackets Name egenskap istället, så det matchar också en katt med namnet Rex:
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
Mönstret not negerar sitt kapslade mönster. I pet is not nullkontrollerar det kapslade null mönstret innehållet, så not null är ett användbart has-a-value-test. Däremot kontrollerar ett tomt otypat egenskapsmönster, { }, unionsinstansen. Den matchar alltid en icke-inullerbar struct-union, även om Value den är null:
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
}
Logiska mönster bevarar dessa skillnader.
var pet I var pet and not nullsamlar in den inkommande unionen och not null testar om den har innehåll som inte är null.
var I not null and var petavbildar mönstret fortfarande den inkommande unionen: not ändrar inte värdet som skickas till det högra mönstret. I skickar däremot Dog and { Breed: "Labrador" }typmönstret det matchade till egenskapsmönstret Dog .
Anmärkning
Ett typmönster måste vara kompatibelt med minst en skiftlägestyp. För deklarationerna i den Pet här artikeln pet is Pet genererar kompilatorfel cs8121: ingen av deras falltyper kan matcha Pet. Det är inte ett körningstest av unionsinstansen. Om du vill söka efter innehåll, använd pet is not null eller pet.Value is not null; för att avbilda själva unionen använder du ett var mönster.
Kort och kort, typ- och deklarationsmönster testar innehållet. Rekursiva mönster med en typ testar också innehållet. rekursiva mönster utan typ använder förbundets egna medlemmar. Dessa regler är desamma i ett is uttryck och ett switch uttryck.
Null-matchning
För struct-fackföreningar null kontrollerar mönstret om Value är null:
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
}
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 över en unions skiftlägestyper när det hanterar varje ärendetyp. Om nullability-analysen även tar hänsyn till innehållet som inte är null behöver du inte ta bort (_) eller 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
}
Om du vill hantera null-innehåll föredrar du en null arm framför en var eller ignorera (_) arm. Mönstret null anger din avsikt och bevarar varningar när du lägger till en ny skiftlägestyp utan matchande arm. En var eller ignorera arm accepterar också alla nya fall, så kompilatorn kan inte varna dig om det saknade fallet.
När det spårade null-tillståndet Value är "kanske null" varnar kompilatorn också om du utelämnar en null arm. Till exempel NullablePet har en nullbar skiftlägestyp, så dess parameterinnehåll kan vara null:
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",
};
Om du null tar bort armen från den här växeln genereras varnings-CS8655.
Nullbar analys garanterar inte att en genererad union har icke-null-innehåll vid körning. En standardunion innehåller null, inklusive när det är ett standardinitierat fält eller matriselement. Om alla skiftlägestyper inte är ogiltiga är standardtillståndet Value för spårat ändå "inte null", så kompilatorn varnar inte nödvändigtvis för en utelämnad null arm. Hantera null för korrekthet när standardinnehållet kan nå din växel:
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
}
I det här Pet exemplet genererar borttagning av null armen ingen varning om att null-arm saknas om inte tidigare kod ändrar det spårade null-tillståndet, men växeln genererar SwitchExpressionException när den tar emot standardvärdet. Ett tidigare is null test kan ändra det spårade tillståndet och orsaka en varning. Det här beteendet är separat från en bestämd tilldelning: kompilatorn avvisar en läsning av en otilldelad lokal variabel.
Nullbarhet
Kompilatorn spårar null-tillståndet för en unions Value egenskap genom följande regler:
- Standardvärdet null för en unions
Valueegenskap är "kanske null" om standardvärdet null för alla falltyper är "kanske null". Annars är standardvärdet null "inte null". - 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 för varje ärendetyp och en Value egenskap. Språket tillhandahåller implicita unionskonverteringar som anropar dessa konstruktorer. Det genererade formuläret är åsiktsutlåtande. Det är alltid en struct, rutor alltid värdetypsfall och lagrar alltid innehåll som object?.
Du kan behöva ett annat beteende om du vill anpassa en befintlig typ, skapa en klassbaserad union eller använda en anpassad lagringsstrategi eller om du behöver interop-stöd. Du kan 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 föregående 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. Din anpassade unionstyp kan lägga till fler medlemmar.
Kompilatorn förutsätter att anpassade unionstyper uppfyller dessa beteenderegler:
-
Ljud:
Valuereturnerarnullalltid eller ett värde för en av skiftlägestyperna, inklusive för ett standardvärde för struct. En anpassad union kan representera standardvärdet som ett icke-null-ärende, till skillnad från nullinnehållet i en standardgenererad union. -
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.TryGetValuereturnerartrueendast närValueär ett icke-null-värde av den typen. Parameternsouttyp är identitetskonverterad till skiftlägestypen eller till den underliggande värdetypen när skiftlägestypen är en nullbar värdetyp.
[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 mönstermatchning anropar TryGetValue kompilatorn typmönsterkontroller (till exempel union is T) och HasValue för null-mönsterkontroller (till exempel union is null), för att undvika boxning för värdetypsfall. Varje medlem gäller för sin egen mönstertyp – inte heller en reserv för den andra. När en medlem saknas kontrollerar Value kompilatorn egenskapen efter det mönstret i stället. Mer information finns i Hur kompilatorn genererar kod för mönstermatchning.
Unionsmedlemsleverantörer
En fackföreningstyp kan delegera sina fackföreningsmedlemmar till ett offentligt kapslat IUnionMembers gränssnitt som den direkt innehåller och implementerar. Det gränssnittet är den fackliga medlemsprovidern. Kompilatorn hittar endast fackföreningsmedlemmar i providergränssnittet, inklusive ärvda medlemmar. Medlemmar som deklareras endast för själva fackföreningstypen används inte som fackföreningsmedlemmar.
När du använder en unionsmedlemsprovider måste gränssnittet deklarera följande obligatoriska medlemmar:
-
Statiska
Createmetoder: En för varje ärendetyp, med en enskild parameter och en returtyp som kan konverteras till union-typen. Dessa metoder etablerar dina ärendetyper. -
Valueegenskap: En offentligobjectegenskap ellerobject?egenskap med engetaccessor som returnerar det inneslutna värdet.
Du kan också deklarera dessa medlemmar i gränssnittet så att kompilatorn genererar effektivare mönstermatchningskod:
-
TryGetValuemetoder: En för varje skiftlägestyp som returnerasboolmed enoutparameter av skiftlägestypen. -
HasValueegenskap: En offentligboolegenskap med engetaccessor som returnerastruenärValueintenullär .
Hur kompilatorn genererar kod för mönstermatchning: TryGetValue och HasValue gäller för olika typer av mönster – inte heller en återställning för den andra:
- För ett typmönster, till exempel
union is T, anroparTryGetValue(out T value)kompilatorn när det deklareras, extraherar värdet och tillämpar mönstret på det utan boxning. I annat fall tillämpar kompilatorn mönstret påValueegenskapen, som utför en körningstypskontroll motToch kan markera värdet närTär en värdetyp. - För ett null-mönster, till exempel
union is null, anroparHasValuekompilatorn när den deklareras för att testa om unionen innehåller ett värde. Annars tillämpar kompilatorn null-mönstret direkt påValueegenskapen.
Varje medlem är oberoende valfritt: utan TryGetValueanvänder typmönster Value; utan HasValueanvänder null-mönster Value. Om du deklarerar någon av medlemarna kan kompilatorn generera effektivare, starkt skriven kod för den mönstertypen:
[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;
}
}
Unionsmedlemsleverantörer är användbara när unionstypen behöver en privat konstruktor eller när skapandelogik kräver ett fabriksmönster, till exempel med record class unionstyper.
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.
Språkspecifikation för C#
Mer information finns i Unions funktionsspecifikation .