Typy sjednocení (referenční dokumentace jazyka C#)

Typ sjednocení představuje hodnotu, která může být jedním z několika typů případů. Sjednocení poskytují implicitní převody z každého typu případu, vyčerpávající porovnávání vzorů a sledování rozšířené hodnoty nullability. Pomocí klíčového union slova deklarujte typ sjednocení:

public union Pet(Cat, Dog, Bird);

Tato deklarace vytvoří sjednocení se třemi Pet typy případů: Cat, Doga Bird. Proměnné můžete přiřadit libovolnou Pet hodnotu typu případu. Kompilátor zajišťuje, že switch výrazy pokrývají všechny typy případů.

Referenční dokumentace jazyka C# dokumentuje naposledy vydané verze jazyka C#. Obsahuje také počáteční dokumentaci k funkcím ve verzi Public Preview pro nadcházející jazykovou verzi.

Dokumentace identifikuje všechny funkce, které byly poprvé představeny v posledních třech verzích jazyka nebo v aktuálních verzích Public Preview.

Návod

Informace o tom, kdy byla funkce poprvé představena v jazyce C#, najdete v článku o historii verzí jazyka C#.

Deklarujte sjednocení, pokud hodnota musí být přesně jednou z pevných množiny typů a chcete, aby kompilátor vynucoval, aby se každá možnost zpracovala. Mezi běžné scénáře patří:

  • Vrátí výsledek nebo chybu: Metoda vrátí hodnotu úspěchu nebo chybovou hodnotu a volající musí zpracovat obojí. Sjednocením je union Result(Success, Error) sada výsledků explicitní.
  • Odesílání zpráv nebo příkazů: Systém zpracovává uzavřenou sadu typů zpráv. Sjednocení zajišťuje, že nové typy zpráv vytvářejí upozornění v době kompilace v každém switch , který je ještě nezpracuje.
  • Nahrazení rozhraní značek nebo abstraktních základních tříd: Pokud používáte rozhraní nebo abstraktní třídu výhradně k seskupení typů pro porovnávání vzorů, sjednocení poskytuje kontrolu úplnosti bez nutnosti dědičnosti nebo sdílených členů.

Sjednocení se liší od jiných deklarací typů důležitými způsoby:

  • class Na rozdíl od nebo structnedefinuje sjednocení nové datové členy. Místo toho vytvoří existující typy do uzavřené sady alternativ.
  • interfaceNa rozdíl od sjednocování je uzavřeno – definujete úplný seznam typů případů v deklaraci a kompilátor použije tento seznam k kontrolám úplnosti.
  • recordNa rozdíl od sjednocení nepřidává chování rovnosti, klonování ani dekonstrukce. Sjednocení se zaměřuje na "jaký případ je?" a ne na to, jaká pole mají?

Typy sjednocení se spoléhají na typy UnionAttributeIUnion v System.Runtime.CompilerServices oboru názvů. Modul runtime .NET 11 obsahuje tyto typy.

Deklarace sjednocení

Deklarace sjednocení určuje název a seznam typů případů:

public union Pet(Cat, Dog, Bird);

Typy případů mohou být libovolný typ, který se převede na objecttřídy, struktury, rozhraní, parametry typu, typy s možnou hodnotou null a další sjednocení. Následující příklady ukazují různé možnosti typů případů:

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);

Pokud je typem případu typ hodnoty (například int), sjednocovací deklarace hodnotu uloží jako jediný object? odkaz. Vlastní typy sjednocení můžou používat jinou strategii úložiště.

Deklarace sjednocení může obsahovat tělo s dalšími členy, stejně jako struktura, s výhradou některých omezení. Deklarace sjednocení nemůžou obsahovat pole instancí, automatické vlastnosti ani události podobné polím. Nemůžete také deklarovat veřejné konstruktory s jedním parametrem, protože kompilátor tyto konstruktory generuje jako členy vytváření sjednocení. Jakýkoli konstruktor, který deklarujete, musí použít this(...) inicializátor k delegování, přímo nebo nepřímo, na vygenerovaný konstruktor. Následující Length sjednocení přidá TotalMeters vlastnost, která používá porovnávání vzorů pro zpracování každého typu případu spolu s metodou Add , která kombinuje dvě délky:

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);
}

Sjednocení převodů

Implicitní sjednocovacího převodu existuje z každého typu případu na typ sjednocení:

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 }
}

Sjednocovací převody fungují voláním odpovídajícího generovaného konstruktoru. Pokud existuje operátor implicitního převodu definovaný uživatelem pro stejný typ, má operátor definovaný uživatelem přednost před sjednocovacího převodu. Pokud je pro zdrojovou hodnotu stejně použitelný více než jeden typ případu, je sjednocování převodu nejednoznačný a kompilátor hlásí chybu. Podrobnosti o prioritě převodu najdete ve specifikaci funkce.

Převod sjednocení na strukturu sjednocení s možnou hodnotou null (T?) funguje také v případech, kdy T je sjednocovací typ:

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",
    };
}

Porovnávání vzorů sjednocení

Při použití vzoru typu nebo deklarace u sjednocovacího výrazu vzor testuje obsah sjednocení, nikoli instanci sjednocení. Například vzor testujeDog, zda Value je :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
}

Formulář vzoru určuje, jakou hodnotu testuje:

Vzor Chování výrazu sjednocení
Typ nebo vzor deklarace, například Dog nebo Dog dog Otestuje obsah podle zadaného typu.
Vlastnost nebo poziční vzor typu, například Dog { Breed: "Labrador" } Otestuje obsah podle typu a pak použije vnořené vzory na danou hodnotu případu.
Vlastnost nebo poziční vzor bez typu, například { Name: "Rex" } Používá vlastnosti nebo Deconstruct metodu sjednocovacího instance.
Vzor seznamu Používá počet a indexování členů instance sjednocení, nikoli jejich obsah.
var nebo zahodit _ vzor Zachytává nebo odpovídá příchozí sjednocovací instanci bez jeho přepsání.
Konstantní nebo relační vzor Otestuje obsah. Vzor null také zpracovává odkaz na sjednocení null nebo obálku s možnou hodnotou null.

Například definujte Pet sjednocení s vlastností jen Name pro čtení, která deleguje na obsaženou Cat vlastnost nebo Dog. Vzory typů ve výrazu vlastnosti switch testují obsah:

public union Pet(Cat, Dog)
{
    public string Name => this switch
    {
        Cat cat => cat.Name,
        Dog dog => dog.Name,
        null => "",
    };
}

Typový vzor Dog { Breed: "Labrador" } testuje obsah a čte psí Breed, vlastnost, která sjednocení nemá. Netypový vzor { Name: "Rex" } místo toho přečte vlastnost sjednocení Name , takže také odpovídá kočkě s názvem 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

Vzor not neguje svůj vnořený vzor. Vnořený pet is not nullnull vzor kontroluje obsah, takže not null je užitečný test hodnot. Naproti tomu prázdný netypový vzor vlastnosti , { }zkontroluje sjednocovat instanci. Vždy odpovídá nenulovatelné sjednocování struktury, i když Value má hodnotu 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
}

Logické vzory zachovávají tyto rozdíly. var pet Zachytí var pet and not nullpříchozí sjednocení a not null otestuje, jestli obsahuje obsah, který nemá hodnotu null. V not null and var petvzor var stále zachycuje příchozí sjednocení: not nezmění hodnotu předanou do pravé ruky. Naproti tomu Dog and { Breed: "Labrador" }vzor typu předává odpovídající Dog vzor vlastnosti.

Poznámka:

Vzor typu musí být kompatibilní s alespoň jedním typem případu. Pet Pro deklarace v tomto článku vytvoří pet is Pet chybu kompilátoru CS8121: žádný z jejich typů případů nemůže odpovídat Pet. Nejedná se o test modulu runtime sjednocovacího instance. Pokud chcete zkontrolovat obsah, použijte pet is not null nebo pet.Value is not null; k zachycení samotné sjednocení použijte var vzor.

Stručně řečeno, vzory typů a deklarací testují obsah. Rekurzivní vzory s typem testují obsah také; rekurzivní vzory bez typu používají vlastní členy sjednocení. Tato pravidla jsou stejná ve výrazu is a výrazu switch .

Porovnávání s hodnotou Null

U sjednocení struktur vzor kontroluje, null jestli Value má hodnotu 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
}

U sjednocení založených na třídách je úspěšné, null pokud je odkaz na sjednocení samotný null nebo jeho Value vlastnost má hodnotu 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 */ }

U typů struktury sjednocení s možnou hodnotou null (Pet?) proběhne úspěšně, null pokud obálka s možnou hodnotou null nemá žádnou hodnotu nebo pokud je podkladová sjednocení Value null.

Úplnost sjednocení

Výraz switch je vyčerpávající pro typy případů sjednocení, když zpracovává každý typ případu. Pokud analýza nullability také bere v úvahu obsah, který není null, nepotřebujete zahodit (_) nebo 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
}

Pokud chcete zpracovat obsah s hodnotou null, upřednostněte null rameno nad ramenem var nebo zahoďte (_). Vzor null uvádí váš záměr a zachovává upozornění, když přidáte nový typ případu bez odpovídajícího ramene. Arm var nebo zahodit také přijme jakýkoli nový případ, takže kompilátor vás nemůže varovat před tímto chybějícím případem.

Pokud je sledovaný stav Value null "možná null", kompilátor také upozorní, pokud vynecháte null ruku. Má například NullablePet typ případu s možnou hodnotou null, takže jeho obsah parametru může mít hodnotu 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",
};

Odebráním ramene null z tohoto přepínače se zobrazí upozornění CS8655.

Analýza s možnou hodnotou null nezaručuje, že vygenerovaná sjednocení má za běhu nenulový obsah. Výchozí sjednocení obsahuje hodnotu null, včetně výchozího inicializovaného pole nebo prvku pole. Pokud jsou všechny typy případů nenulovatelné, výchozí sledovaný stav Value je přesto "not null", takže kompilátor nemusí nutně varovat o vynechané null arm. Zpracujte null správnost, kdykoli se k vašemu přepínači dostane výchozí obsah:

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
}

V tomto Pet příkladu null odebrání arm nevygeneruje žádné upozornění chybějící-null-arm, pokud dřívější kód nezmění sledovaný stav null, ale přepínač vyvolá SwitchExpressionException , když obdrží výchozí hodnotu. Předchozí is null test může změnit sledovaný stav a způsobit upozornění. Toto chování je oddělené od určitého přiřazení: kompilátor odmítne čtení nepřiřazené místní proměnné.

Nullovatelnost

Kompilátor sleduje stav null vlastnosti sjednocení Value pomocí následujících pravidel:

  • Výchozí stav null vlastnosti sjednocení Value je "možná null", pokud výchozí stav null jakéhokoli typu případu je "možná null". Jinak výchozí stav null není null.
  • Když vytvoříte sjednocovací hodnotu z typu případu (prostřednictvím konstruktoru nebo sjednocovacího převodu), Value získá stav null příchozí hodnoty.
  • Při dotazování obsahu sjednocovacího modelu HasValue nebo TryGetValue(...) členů modelu přístupu se stav Value null ve větvi změní na true hodnotu "not null".

Vlastní typy sjednocení

Kompilátor převede union deklaraci na struct deklaraci. Struktura je označena atributem [System.Runtime.CompilerServices.Union] a implementuje IUnion rozhraní. Obsahuje veřejný konstruktor pro každý typ případu a Value vlastnost. Jazyk poskytuje implicitní sjednocovací převody, které volají tyto konstruktory. Tento vygenerovaný formulář je názorný. Vždy se jedná o strukturu, vždy pole hodnotových případů a vždy ukládá obsah jako object?.

Pokud chcete přizpůsobit existující typ, vytvořit sjednocení založené na třídách nebo použít vlastní strategii úložiště nebo pokud potřebujete podporu spolupráce, může být potřeba jiné chování. Sjednocovacího typu můžete vytvořit ručně.

Jakákoli třída nebo struktura s atributem [Union] je typ sjednocení , pokud se řídí základním vzorem sjednocení. Základní sjednocovacího vzoru vyžaduje:

  • Atribut [Union] typu.
  • Jeden nebo více veřejných konstruktorů, z nichž každý má jednu hodnotu nebo in parametr. Typ parametru každého konstruktoru definuje typ případu.
  • Veřejná Value vlastnost typu object? (nebo object) s příslušenstvím get .

Všechny předchozí členy odborů musí být veřejné. Kompilátor tyto členy používá k implementaci sjednocovacího převodu, porovnávání vzorů a kontrol úplnosti. Můžete také implementovat vzor přístupu bez rámečku nebo vytvořit typ sjednocení založené na třídách. Vlastní typ sjednocení může přidat další členy.

Kompilátor předpokládá, že vlastní typy sjednocení vyhovují těmto pravidlům chování:

  • Zvuk:Value vždy vrátí null hodnotu jednoho z typů případů, včetně výchozí hodnoty struktury. Vlastní sjednocení může představovat výchozí hodnotu jako nenulový případ, na rozdíl od obsahu null výchozí vygenerované sjednocení.
  • Stabilita: Pokud vytvoříte sjednocovat hodnotu z typu případu, Value odpovídá tomuto typu případu (nebo je-li null vstup null).
  • Ekvivalence vytvoření: Pokud je hodnota implicitně konvertibilní na dva různé typy případů, oba členy vytváření vytvářejí stejné pozorovatelné chování.
  • Konzistence vzorů přístupu: Členové HasValue a TryGetValue členové se chovají stejně jako při kontrole Value přímo.

Následující příklad ukazuje vlastní typ sjednocení:

[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
}

Vzor přístupu bez boxování

Vlastní sjednocovací typ může volitelně implementovat vzor přístupu bez boxingu , který umožňuje přístup silného typu k případům typu hodnota bez boxování během porovnávání vzorů. Tento model vyžaduje:

  • Vlastnost HasValue typu bool , která vrací true , když Value není null.
  • Metoda TryGetValue pro každý typ případu, který vrátí bool a doručí hodnotu prostřednictvím parametru out . TryGetValue vrátí true pouze v případě, že Value je hodnota jiného typu než null daného typu případu. Typ out parametru je konvertibilní na typ případu nebo na typ podkladové hodnoty, pokud je typ případu typu hodnoty null.
[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
}

Pro porovnávání vzorů kompilátor volá TryGetValue kontroly typu (například union is T) a HasValue kontroly vzoru s hodnotou null (například union is null), aby se zabránilo boxování pro případy typu hodnota. Každý člen se vztahuje na vlastní druh vzoru – ani jeden z nich není náhradní pro druhý. Pokud člen chybí, kompilátor místo toho zkontroluje Value vlastnost daného vzoru. Další informace naleznete v tématu Jak kompilátor generuje kód pro porovnávání vzorů.

Poskytovatelé členů Unie

Typ sjednocení může členy sjednocení delegovat na veřejné vnořené IUnionMembers rozhraní, které přímo obsahuje a implementuje. Toto rozhraní je zprostředkovatelem člena sjednocení. Kompilátor najde členy sjednocení pouze v rozhraní zprostředkovatele, včetně zděděných členů. Členy deklarované pouze u samotného typu sjednocení se nepoužívají jako členové sjednocení.

Při použití zprostředkovatele člena sjednocení musí rozhraní deklarovat tyto požadované členy:

  • Statické Create metody: Jedna pro každý typ případu s jedním parametrem a návratovým typem identity se konvertibilním typem sjednocení. Tyto metody navazují typy případů.
  • Value vlastnost: Veřejná object vlastnost nebo object? vlastnost s příslušenstvím get , který vrací hodnotu obsaženou.

Volitelně můžete deklarovat tyto členy v rozhraní, aby kompilátor vygeneroval efektivnější kód porovnávání vzorů:

  • TryGetValue metody: Jeden pro každý typ případu, který se vrací bool s out parametrem typu případu.
  • HasValue vlastnost: Veřejná bool vlastnost s přistupujícím true objektemget, pokud Value není null.

Jak kompilátor generuje kód pro porovnávání vzorů: TryGetValue a HasValue použije se na různé druhy vzorů – ani jeden z nich není náhradním kódem pro druhý:

  • U vzoru typu, jako union is Tje například , kompilátor volá TryGetValue(out T value) při deklaraci, extrahuje hodnotu a použije na něj vzor bez boxování. V opačném případě kompilátor použije vzor na Value vlastnost, která provádí kontrolu T typu modulu runtime a může hodnotu, pokud T je typ hodnoty.
  • Pro vzor null, například union is null, kompilátor volá HasValue při deklarování, zda sjednocení obsahuje hodnotu. V opačném případě kompilátor použije vzor null přímo na Value vlastnost.

Každý člen je nezávisle volitelný: bez TryGetValue, vzory typů se používají Value; bez HasValue, vzory null používají Value. Deklarování některého člena umožňuje kompilátoru generovat efektivnější kód silného typu pro tento druh vzoru:

[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;
    }
}

Poskytovatelé členů unie jsou užiteční, když typ sjednocení potřebuje privátní konstruktor nebo když logika vytváření vyžaduje vzor továrny, například s record class typy sjednocení.

Typy sjednocení založené na třídách

Třída může být také sjednocovacího typu. Tento typ sjednocení je užitečný v případě, že potřebujete referenční sémantiku nebo dědičnost:

[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",
    };
}

U sjednocení založených na třídách null vzor odpovídá odkazu null i hodnotě null Value.

specifikace jazyka C#

Další informace najdete ve specifikaci funkcí Sjednocení .

Viz také