Megosztás a következőn keresztül:


Egyesítő típusok (C#-referencia)

Az egyesítő típus olyan értéket jelöl, amely több esettípus egyike lehet. Az egyes esettípusok implicit konverzióit, a teljes mintaegyezést és a továbbfejlesztett nullability trackingt biztosítják. union A kulcsszóval deklarálhat egy egyesítési típust:

public union Pet(Cat, Dog, Bird);

Ez a deklaráció három esettípussal hoz létre egy Pet egyesítést: Cat, Dogés Bird. Bármilyen esettípus-értéket hozzárendelhet egy Pet változóhoz. A fordító biztosítja, hogy a switch kifejezések minden esettípusra kiterjednek.

A C# nyelv referenciadokumentuma a C# nyelv legújabb kiadású verzióját ismerteti. Emellett a közelgő nyelvi kiadás nyilvános előzetes verziójú funkcióinak kezdeti dokumentációját is tartalmazza.

A dokumentáció azonosítja azokat a funkciókat, amelyeket először a nyelv utolsó három verziójában vagy az aktuális nyilvános előzetes verziókban vezetnek be.

Jótanács

Ha meg szeretné tudni, hogy mikor jelent meg először egy funkció a C#-ban, tekintse meg a C# nyelvi verzióelőzményeiről szóló cikket.

Deklaráljon egy egyesítést, ha egy értéknek pontosan egy rögzített típuskészletnek kell lennie, és azt szeretné, hogy a fordító kényszerítse minden lehetőség kezelését. Gyakori forgatókönyvek a következők:

  • Eredmény vagy hiba: A metódus sikerértéket vagy hibaértéket ad vissza, a hívónak mindkettőt kezelnie kell. Egy ilyen union Result(Success, Error) egyesítés explicitvé teszi az eredmények halmazát.
  • Üzenet- vagy parancsküldés: A rendszer egy zárt üzenettípus-készletet dolgoz fel. Az egyesítők biztosítják, hogy az új üzenettípusok fordítási idejű figyelmeztetéseket készítsenek minden switch olyan üzenetnél, amely még nem kezeli őket.
  • Jelölőillesztők vagy absztrakt alaposztályok cseréje: Ha egy felületi vagy absztrakt osztályt kizárólag a mintaegyeztetés típusainak csoportosítására használ, a egyesítés teljes körű ellenőrzést biztosít öröklés vagy megosztott tagok megkövetelése nélkül.

Az egyesítők fontos módon különböznek a többi típusdeklarációtól:

  • Egy vagy structtöbb class egyesítéssel ellentétben az egyesítők nem határoznak meg új adattagokat. Ehelyett a meglévő típusokat egy zárt alternatívahalmazba állítja össze.
  • interfaceA egyesítéstől eltérően a deklarációban meg kell határoznia az esettípusok teljes listáját, és a fordító ezt a listát használja a teljesség ellenőrzéséhez.
  • recordA egyesítéssel ellentétben a egyesítés nem ad egyenlőséget, klónozást vagy dekonstruálási viselkedést. Az egyesítők inkább a "melyik esetet" helyezik előtérbe, nem pedig a "milyen mezőkkel rendelkeznek?"

Fontos

A .NET 11 Preview 2-ben a futtatókörnyezet nem tartalmazza a felületet és IUnion a UnionAttribute felületet. Az egyesítő típusok használatához saját maga kell deklarálnia őket. A szükséges nyilatkozatok megtekintéséhez tekintse meg az uniós végrehajtást.

Uniós nyilatkozatok

Az egyesítő deklarációk az esettípusok nevét és listáját határozzák meg:

public union Pet(Cat, Dog, Bird);

Az esettípusok bármilyen típusúak lehetnek, amelyek osztályokat object, szerkezeteket, interfészeket, típusparamétereket, null értékű típusokat és más egyesítőket is átalakítanak. Az alábbi példák különböző esettípus-lehetőségeket mutatnak be:

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

Ha egy esettípus értéktípus (például int), akkor az érték akkor lesz bekeretítve, ha az egyesítő Value tulajdonságában van tárolva. A szakszervezetek egyetlen object? hivatkozásként tárolják a tartalmukat.

Az egyesítő nyilatkozatok tartalmazhatnak olyan testületet is, amely további tagokkal rendelkezik, csakúgy, mint egy struktúra, bizonyos korlátozások mellett. Az egyesítő deklarációk nem tartalmazhatnak példánymezőket, automatikus tulajdonságokat vagy mezőszerű eseményeket. A nyilvános konstruktorok egyetlen paraméterrel sem deklarálhatók, mert a fordító ezeket a konstruktorokat egyesítő létrehozási tagokként hozza létre:

public union OneOrMore<T>(T, IEnumerable<T>)
{
    public IEnumerable<T> AsEnumerable() => Value switch
    {
        T single => [single],
        IEnumerable<T> multiple => multiple,
        _ => []
    };
}

Uniós átalakítások

Az egyes esettípusoktól az egyesítő típusig implicit egyesítési átalakítás létezik. Nem kell explicit módon meghívnia egy konstruktort:

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

Az egyesítő átalakítások a megfelelő generált konstruktor meghívásával működnek. Ha egy felhasználó által definiált implicit konverziós operátor létezik ugyanahhoz a típushoz, a felhasználó által definiált operátor elsőbbséget élvez az egyesítő átalakítással szemben. A konvertálási prioritással kapcsolatos részletekért tekintse meg a nyelvi specifikációt.

A null értékű egyesítési szerkezetre (T?) való egyesítés akkor is működik, ha T egy egyesítési típus:

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

Unió egyeztetése

Ha a minta egyezik az egyesítő típussal, a minták az unió tulajdonságára Value vonatkoznak, nem magára az egyesítő értékre. Ez a "feloldás" viselkedés azt jelenti, hogy az egyesítés transzparens a mintaegyezéshez:

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
}

A szabály alól két minta kivételt képez: a var minta és az elvetési _ minta az egyesítő értékre vonatkozik, nem pedig a tulajdonságára Value . Az egyesítő érték Pet?GetPet() rögzítésére használható var a (Nullable<Pet>):

if (GetPet() is var pet) { /* pet is the Pet? value returned from GetPet */ }

A logikai mintákban minden ág egyenként követi a kibontási szabályt. Az alábbi minta azt ellenőrzi, hogy a Pet? nem null érték és az értéke Value nem null-e:

GetPet() switch
{
    var pet and not null => ..., // 'var pet' captures the Pet?; 'not null' checks Value
}

Megjegyzés:

Mivel a minták vonatkoznak rá Value, az olyan minták, mint pet is Pet általában nem egyeznek, mivel Pet az unió tartalmával van tesztelve, és nem magát az uniót.

Null egyezés

A struct unions esetében a null minta ellenőrzi, hogy null-e Value :

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
}

Osztályalapú egyesítések esetén akkor sikeres, null ha az egyesítő hivatkozás maga null, vagy a tulajdonsága Value 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 */ }

A null értékű egyesítőszerkezet-típusok (Pet?) null akkor sikeresek, ha a null értékű burkolónak nincs értéke, vagy ha a mögöttes egyesítés Value null értékű.

Az unió teljessége

A switch kifejezések teljes körűek, ha az összes egyesítő esettípust kezeli. A fordító csak akkor figyelmeztet, ha a kis- és nagybetűket nem kezeli. Nincs szükség elvetési mintára (_) vagy var mintára, hogy megfeleljen bármilyen típusnak:

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
}

Ha az unió Value tulajdonságának null állapota "talán null", akkor a figyelmeztetés elkerülése érdekében a következőt kell kezelnie 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
}

Null lehetőség

A fordító az alábbi szabályokkal követi nyomon az unió tulajdonságának Value null állapotát:

  • Ha egy esettípusból (konstruktoron vagy egyesítési átalakításon keresztül) hoz létre egyesítő értéket, Value a bejövő érték null állapotát kapja meg.
  • Amikor a nem boxing hozzáférési minta HasValue vagy TryGetValue(...) tagok lekérdezik az unió tartalmát, az ág null állapota Value "nem null" true lesz.

Egyéni egyesítő típusok

A fordító deklarációvá union alakítja a deklarációt struct . A szerkezet az attribútummal [System.Runtime.CompilerServices.Union] van megjelölve, megvalósítja az interfészt IUnion . Tartalmaz egy nyilvános konstruktort és egy implicit konverziót minden esettípushoz egy Value tulajdonsággal együtt. A létrehozott űrlap véleményezhető. Ez mindig egy struktúra, mindig érték típusú eseteket ad meg, és a tartalmat object?mindig a következőképpen tárolja.

Ha eltérő viselkedésre van szüksége – például osztályalapú unióra, egyéni tárolási stratégiára, interop-támogatásra vagy meglévő típus módosítására – manuálisan is létrehozhat egy egyesítő típust.

Bármely attribútummal rendelkező [Union] osztály vagy struktúra egyesítő típus , ha az alapszintű egyesítési mintát követi. Az alapszintű egyesítő minta a következőt igényli:

  • Egy [Union] attribútum a típuson.
  • Egy vagy több nyilvános konstruktor, amelyek mindegyike egyetlen értékkel vagy in paraméterrel rendelkezik. Az egyes konstruktorok paramétertípusa egy esettípust határoz meg.
  • Olyan köztulajdon Value , amelynek típusa object? (vagy object) tartozékkal van eltulajdonítva get .

Minden szakszervezeti tagnak nyilvánosnak kell lennie. A fordító ezeket a tagokat használja az egyesítő átalakítások, a minták egyeztetése és a teljesség ellenőrzése érdekében. Implementálhatja a nem boxing hozzáférési mintát is, vagy létrehozhat egy osztályalapú uniótípust.

A fordító feltételezi, hogy az egyéni egyesítő típusok megfelelnek az alábbi viselkedési szabályoknak:

  • Hang:Value mindig az egyik esettípus értékét adja vissza null – soha ne adjon meg más típusú értéket. A struct unions esetében default a Valuenull.
  • Stabilitás: Ha egyesítő értéket hoz létre egy kis- és nagybetűtípusból, Value akkor az adott esettípusnak megfelelő (vagy null ha a bemenet volt null).
  • Létrehozási egyenértékűség: Ha egy érték implicit módon két különböző esettípusra konvertálható, mindkét létrehozási tag ugyanazt a megfigyelhető viselkedést hozza létre.
  • Hozzáférési minta konzisztenciája: A HasValueTryGetValue tagok és ha vannak, ugyanúgy viselkednek, mint a közvetlen ellenőrzés Value .

Az alábbi példa egy egyéni egyesítő típust mutat be:

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

Nem boxing hozzáférési minta

Az egyéni egyesítési típusok opcionálisan implementálhatják a nem dobozoló hozzáférési mintát , hogy az érték típusú esetekhez való erős hozzáférést lehetővé tegyék anélkül, hogy a mintaegyezés során boxolást hajtanak végre. Ehhez a mintához a következők szükségesek:

  • Olyan HasValue típusú bool tulajdonság, amely akkor ad true vissza, ha Value nem null.
  • Egy TryGetValue metódus minden egyes esettípushoz, amely egy paraméteren keresztül out adja vissza bool és kézbesíti az értéket.
[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
}

A fordító előnyben részesíti a tulajdonságot TryGetValue a Value mintaegyeztetés megvalósításakor, ami elkerüli az értéktípusok bevitelét.

Osztályalapú egyesítő típusok

Az osztály lehet egyesítő típus is. Az ilyen típusú egyesítés akkor hasznos, ha referencia szemantikára vagy öröklésre van szüksége:

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

Az osztályalapú egyesítések esetében a null minta egy null hivatkozással és null értékkel Valueis megegyezik.

Uniós végrehajtás

Az alábbi attribútum és felület támogatja az egyesítő típusokat fordításkor és futtatókörnyezetben:

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
    public sealed class UnionAttribute : Attribute;

    public interface IUnion
    {
        object? Value { get; }
    }
}

A fordító IUnionáltal létrehozott uniós deklarációk. Az egyesítő értékeket futásidőben a következővel IUnionellenőrizheti:

if (value is IUnion { Value: null }) { /* the union's value is null */ }

Amikor deklarál egy típust union , a fordító létrehoz egy szerkezetet, amely megvalósítja azokat IUnion. A deklaráció (public union Pet(Cat, Dog, Bird);) például Pet a következőnek felel meg:

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

Fontos

A .NET 11 Preview 2-ben ezek a típusok nem szerepelnek a futtatókörnyezetben. Az egyesítő típusok használatához deklarálnia kell őket a projektben. Ezek szerepelni fognak egy jövőbeli .NET-előzetesben.

C# nyelvspecifikáció

További információ: Unions feature specification.

Lásd még