Delen via


Samenvoegtypen (C#-verwijzing)

Een samenvoegtype vertegenwoordigt een waarde die een van de verschillende casetypen kan zijn. Samenvoegingen bieden impliciete conversies van elk casetype, volledige patroonkoppeling en verbeterde tracering van null-waarden. Gebruik het union trefwoord om een samenvoegtype te declareren:

public union Pet(Cat, Dog, Bird);

Met deze declaratie wordt een Pet samenvoeging gemaakt met drie casetypen: Cat, Dogen Bird. U kunt elke waarde van het casetype toewijzen aan een Pet variabele. De compiler zorgt ervoor dat switch expressies alle casetypen behandelen.

De C#-taalreferentiedocumenten beschrijven de meest recent uitgebrachte versie van de C#-taal. Het bevat ook de eerste documentatie voor functies in openbare previews voor de aanstaande taalrelease.

De documentatie identificeert alle functies die voor het eerst zijn geïntroduceerd in de laatste drie versies van de taal of in de huidige openbare previews.

Aanbeveling

Raadpleeg het artikel over de versiegeschiedenis van de C#-taal om te achterhalen wanneer een functie voor het eerst is geïntroduceerd in C#.

Declareer een samenvoeging wanneer een waarde precies een van een vaste set typen moet zijn en u wilt dat de compiler afdwingt dat elke mogelijkheid wordt afgehandeld. Veelvoorkomende scenario's zijn onder andere:

  • Resultaat-of-fout retourneert: Een methode retourneert een succeswaarde of een foutwaarde en de aanroeper moet beide afhandelen. Een samenvoeging zoals union Result(Success, Error) maakt de set resultaten expliciet.
  • Bericht- of opdrachtverzending: een systeem verwerkt een gesloten set berichttypen. Een samenvoeging zorgt ervoor dat nieuwe berichttypen compilatietijdwaarschuwingen produceren voor elk switch bericht dat deze nog niet verwerkt.
  • Markeringsinterfaces of abstracte basisklassen vervangen: Als u een interface of abstracte klasse alleen gebruikt om typen te groeperen voor patroonkoppeling, biedt een samenvoeging u volledigheidscontrole zonder overname of gedeelde leden te vereisen.

Een samenvoeging verschilt van andere typedeclaraties op belangrijke manieren:

  • In tegenstelling tot een class of structdefinieert een samenvoeging geen nieuwe gegevensleden. In plaats daarvan bestaat het uit bestaande typen in een gesloten set alternatieven.
  • In tegenstelling tot een interface, wordt een samenvoeging gesloten: u definieert de volledige lijst met casetypen in de declaratie en de compiler gebruikt die lijst voor volledigheidscontroles.
  • In tegenstelling tot een record, voegt een samenvoeging geen gelijkheids-, kloon- of destructiegedrag toe. Een samenvoeging richt zich op 'welk geval is het?' in plaats van 'welke velden heeft het?'

Belangrijk

In .NET 11 Preview 2 bevat UnionAttribute de runtime de en IUnion interface niet. Als u samenvoegtypen wilt gebruiken, moet u deze zelf declareren. Zie de tenuitvoerlegging van de Unie om de vereiste verklaringen te bekijken.

Verklaringen van de Unie

Een samenvoegdeclaratie geeft een naam en een lijst met casetypen op:

public union Pet(Cat, Dog, Bird);

Casetypen kunnen elk type zijn dat wordt geconverteerd naar object, inclusief klassen, structs, interfaces, typeparameters, null-typen en andere samenvoegingen. In de volgende voorbeelden ziet u verschillende mogelijkheden voor casetypen:

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

Wanneer een casetype een waardetype is (zoals int), wordt de waarde in het vak opgeslagen in de eigenschap van Value de samenvoeging. Unions slaan hun inhoud op als één object? verwijzing.

Een samenvoegingsdeclaratie kan een orgaan met extra leden bevatten, net als een struct, onder bepaalde beperkingen. Samenvoegdeclaraties kunnen geen exemplaarvelden, auto-eigenschappen of veldachtige gebeurtenissen bevatten. U kunt ook geen openbare constructors met één parameter declareren, omdat de compiler deze constructors genereert als leden van het maken van een samenvoeging:

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

Samenvoegconversies

Er bestaat een impliciete samenvoegingsconversie van elk casetype naar het samenvoegtype. U hoeft geen constructor expliciet aan te roepen:

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

Samenvoegconversies werken door de bijbehorende gegenereerde constructor aan te roepen. Als er een door de gebruiker gedefinieerde impliciete conversieoperator bestaat voor hetzelfde type, heeft de door de gebruiker gedefinieerde operator voorrang op de samenvoegconversie. Zie de taalspecificatie voor meer informatie over conversieprioriteit.

Een samenvoegingsconversie naar een struct (T?) werkt ook wanneer T een samenvoegingstype is:

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

Samenvoeging

Wanneer u een patroon toepast op een samenvoegtype, zijn patronen van toepassing op de eigenschap van Value de samenvoeging, niet op de samenvoegwaarde zelf. Dit 'uitpakken'-gedrag betekent dat de samenvoeging transparant is voor patroonkoppeling:

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
}

Twee patronen zijn uitzonderingen op deze regel: het var patroon en het verwijderingspatroon _ zijn van toepassing op de samenvoegwaarde zelf, niet Value op de eigenschap ervan. Gebruik var dit om de samenvoegwaarde vast te leggen wanneer GetPet() een Pet? (Nullable<Pet>):

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

In logische patronen volgt elke vertakking de uitgepakte regel afzonderlijk. Met de volgende patroontests wordt getest dat de Pet? waarde niet null is enValue dat het niet null is:

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

Opmerking

Omdat patronen van toepassing zijn Valueop, komt een patroon zoals pet is Pet gewoonlijk niet overeen, omdat Pet deze wordt getest op de inhoud van de samenvoeging, niet op de samenvoeging zelf.

Null-overeenkomende waarden

Voor struct-samenvoegingen controleert het null patroon of Value null is:

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
}

Voor op klassen gebaseerde samenvoegingen null slaagt wanneer de samenvoeging zelf null is of Value de eigenschap null is:

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

Voor structtypen voor null-waarden (Pet?), null slaagt het wanneer de waarde van de nullable wrapper geen waarde heeft of wanneer de onderliggende samenvoeging Value null is.

Uitputtendheid van de Unie

Een switch expressie is volledig wanneer alle casetypen van een samenvoeging worden verwerkt. De compiler waarschuwt alleen als een casetype niet wordt verwerkt. U hoeft geen verwijderingspatroon (_) of var patroon op te nemen om aan elk type te voldoen:

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
}

Als de null-status van de eigenschap van de samenvoeging Value 'misschien null' is, moet u ook afhandelen null om een waarschuwing te voorkomen:

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
}

Nullbaarheid

De compiler houdt de null-status van de eigenschap van een samenvoeging Value bij via de volgende regels:

  • Wanneer u een samenvoegwaarde maakt op basis van een casetype (via een constructor of samenvoegconversie), Value wordt de null-status van de binnenkomende waarde opgehaald.
  • Wanneer de niet-boksende toegangspatronen HasValue of TryGetValue(...) leden de inhoud van Value de samenvoeging opvragen, wordt de null-status 'not null' op de true vertakking.

Aangepaste samenvoegtypen

De compiler converteert een union declaratie naar een struct declaratie. De struct is gemarkeerd met het [System.Runtime.CompilerServices.Union] kenmerk en implementeert de IUnion interface. Het bevat een openbare constructor en een impliciete conversie voor elk casetype, samen met een Value eigenschap. Dat gegenereerde formulier is geïmteerd. Het is altijd een struct, altijd vakken waarde-type cases en slaat altijd inhoud op als object?.

Wanneer u een ander gedrag nodig hebt, zoals een op klassen gebaseerde samenvoeging, een aangepaste opslagstrategie, ondersteuning voor interop of als u een bestaand type wilt aanpassen, kunt u handmatig een samenvoegtype maken.

Elke klasse of struct met een [Union] kenmerk is een samenvoegingstype als dit het basispatroon van de samenvoeging volgt. Voor het eenvoudige samenvoegpatroon is het volgende vereist:

  • Een [Union] kenmerk voor het type.
  • Een of meer openbare constructors, elk met één by-value of in parameter. Het parametertype van elke constructor definieert een casetype.
  • Een openbare Value eigenschap van het type object? (of object) met een get toegangsbeheerprogramma.

Alle leden van de unie moeten openbaar zijn. De compiler gebruikt deze leden om samenvoegconversies, patroonkoppelingen en volledigheidscontroles te implementeren. U kunt ook het toegangspatroon voor niet-boksen implementeren of een op klassen gebaseerd samenvoegtype maken.

De compiler gaat ervan uit dat aangepaste samenvoegtypen voldoen aan deze gedragsregels:

  • Geluidsvermogen: Value retourneert null altijd of een waarde van een van de casetypen- nooit een waarde van een ander type. Voor struct unions default produceert een Value van null.
  • Stabiliteit: Als u een samenvoegwaarde maakt op basis van een casetype, Value komt dit overeen met dat casetype (of null als de invoer was null).
  • Gelijkwaardigheid creëren: als een waarde impliciet wordt omgezet in twee verschillende casetypen, produceren beide creatieleden hetzelfde waarneembare gedrag.
  • Consistentie van toegangspatroon: de HasValue en TryGetValue leden, indien aanwezig, gedragen zich op dezelfde manier als het rechtstreeks controleren Value .

In het volgende voorbeeld ziet u een aangepast type samenvoeging:

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

Toegangspatroon voor niet-boksen

Een aangepast samenvoegingstype kan eventueel het toegangspatroon voor niet-boksen implementeren om sterk getypte toegang tot waarde-type gevallen mogelijk te maken zonder boksen tijdens patroonkoppeling. Voor dit patroon is het volgende vereist:

  • Een HasValue eigenschap van het type bool dat retourneert true wanneer Value dat niet nullis.
  • Een TryGetValue methode voor elk casetype dat de waarde retourneert bool en levert via een out parameter.
[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
}

De compiler geeft TryGetValue de voorkeur aan de eigenschap bij het Value implementeren van patroonkoppeling, waardoor bokswaardetypen worden vermeden.

Op klassen gebaseerde samenvoegtypen

Een klasse kan ook een samenvoegtype zijn. Dit type samenvoeging is handig wanneer u verwijzings-semantiek of overname nodig hebt:

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

Voor op klassen gebaseerde samenvoegingen komt het null patroon overeen met zowel een null-verwijzing als een null-verwijzing Value.

Implementatie van de Unie

Het volgende kenmerk en de interface ondersteunen samenvoegtypen tijdens het compileren en runtime:

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

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

Union-declaraties die zijn gegenereerd door de compiler implementeren IUnion. U kunt controleren op elke samenvoegwaarde tijdens runtime met behulp van IUnion:

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

Wanneer u een union type declareert, genereert de compiler een struct die wordt geïmplementeerd IUnion. De declaratie (public union Pet(Cat, Dog, Bird);) wordt bijvoorbeeld Pet gelijk aan:

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

Belangrijk

In .NET 11 Preview 2 zijn deze typen niet opgenomen in de runtime. Als u samenvoegtypen wilt gebruiken, moet u deze declareren in uw project. Deze worden opgenomen in een toekomstige .NET-preview.

C#-taalspecificatie

Zie de functiespecificatie van Unions voor meer informatie.

Zie ook