Delen via


Vakbonden

Opmerking

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Kampioensprobleem: https://github.com/dotnet/csharplang/issues/9662

Overzicht

Unions is een set met onderling gekoppelde functies, die samen C#-ondersteuning bieden voor samenvoegtypen:

  • Samenvoegtypen: Structs en klassen met een [Union] kenmerk worden herkend als samenvoegtypen en ondersteunen het gedrag van de samenvoeging.
  • Casetypen: Samenvoegtypen hebben een set casetypen, die worden gegeven door parameters voor constructors en fabrieksmethoden.
  • Union behaviors: Union types ondersteunen de volgende union behaviors:
    • Samenvoegingsconversies: Er zijn impliciete samenvoegingsconversies van elk casetype naar een samenvoegingstype.
    • Samenvoeging: Patroonkoppeling op basis van samenvoegwaarden wordt impliciet 'uitpakken' van hun inhoud, waarbij het patroon wordt toegepast op de onderliggende waarde.
    • Uitputtendheid van de samenvoeging: schakelexpressies over samenvoegwaarden zijn volledig wanneer alle casetypen overeenkomen, zonder dat er een terugvalcase nodig is.
    • Samenvoegbaarheid: Analyse van nullabiliteit heeft het bijhouden van de null-status van de inhoud van een samenvoeging verbeterd.
  • Samenvoegpatronen: Alle samenvoegtypen volgen een basispatroon voor samenvoeging, maar er zijn aanvullende optionele patronen voor specifieke scenario's.
  • Union-declaraties: Met een verkorte syntaxis kunnen samenvoegtypen rechtstreeks worden declaraties van samenvoegtypen toegestaan. De implementatie is 'opinionated': een struct-verklaring die het basispatroon van de samenvoeging volgt en de inhoud opslaat als één verwijzingsveld.
  • Unieinterfaces: enkele interfaces zijn bekend door de taal en worden gebruikt bij de uitvoering van declaraties van de unie.

Motivatie

Samenvoegingen zijn een lang aangevraagde C#-functie, waarmee waarden uit een gesloten set typen kunnen worden weergegeven op een manier die het vergelijken van patronen volledig kan vertrouwen.

Door de scheiding tussen samenvoegingstypen en samenvoegdeclaraties kan C# een beknopte syntaxis van de samenvoegingsdeclaratie hebben met de mening van semantiek, terwijl ook bestaande typen of typen met andere implementatieopties kunnen worden gebruikt om zich aan te passen aan samenvoeggedrag.

De voorgestelde vakbonden in C# zijn samenvoegingen van typen en niet "gediscrimineerd" of "gelabeld". "Gediscrimineerde vakbonden" kunnen worden uitgedrukt in termen van "type unions" met behulp van nieuwe typedeclaraties als casetypen. U kunt ze ook implementeren als een gesloten hiërarchie, een andere, gerelateerde, aanstaande C#-functie die gericht is op volledigheid.

Gedetailleerd ontwerp

Uniontypen

Elk klasse- of structtype met een System.Runtime.CompilerServices.UnionAttribute kenmerk wordt beschouwd als een samenvoegingstype:

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

Een uniontype moet een bepaald patroon van leden van de openbare unie volgen, die moeten worden gedeclareerd voor het type union zelf of moeten worden gedelegeerd aan een "provider van leden van de unie".

Sommige leden van de unie zijn verplicht en andere zijn optioneel.

Een samenvoegingstype heeft een set casetypen die zijn ingesteld op basis van de handtekeningen van bepaalde leden van de unie.

De inhoud van een samenvoegwaarde kan worden geopend via een Value eigenschap. In de taal wordt ervan uitgegaan dat Value alleen een waarde van een van de casetypen of null bevat (zie Goed gevormdheid).

Unielidproviders

Standaard worden union-leden gevonden op het type union zelf. Als het samenvoegtype echter rechtstreeks een declaratie van een interface met de naam IUnionMembers bevat, fungeert de interface als een provider van unieleden. In dat geval worden leden van de unie alleen gevonden op de provider van unieleden, niet op het type union zelf.

Een interface van een union member provider moet openbaar zijn en het type union zelf moet deze implementeren als een interface.

We gebruiken het type voor het definiëren van de term union voor het type waar de union-leden worden gevonden: de provider van unionleden als deze bestaat en het union-type zelf anders.

Leden van de Unie

Leden van de Unie worden opgezoekd op naam en handtekening voor het type samenvoeging. Ze hoeven niet rechtstreeks te worden gedeclareerd voor het type samenvoeging, maar kunnen worden overgenomen.

Het is een fout dat een lid van de unie niet openbaar is.

De leden van het maken en de Value eigenschap zijn verplicht en worden gezamenlijk het basispatroon van de vereniging genoemd.

De HasValue en TryGetValue leden worden gezamenlijk aangeduid als het toegangspatroon voor niet-boksen union.

De verschillende leden van de unie worden in het volgende beschreven.

Leden voor het maken van de Unie

Leden van het maken van een samenvoeging worden gebruikt om nieuwe samenvoegwaarden te maken op basis van een waarde van het casetype.

Als het type samenvoeging het samenvoegtype zelf is, is elke constructor met één parameter een samenvoegconstructor. De casetypen van de samenvoeging worden geïdentificeerd als de set typen die zijn gebouwd op basis van parametertypen van deze constructors op de volgende manier:

  • Als het parametertype een null-type is (of een waarde of verwijzing), is het casetype het onderliggende type
  • Anders is het casetype het parametertype.
// Union constructor making `Dog` a case type
public Pet(Dog value) { ... }
// Union constructor making `int` a case type
public Union(int? value) { ... }
// Union constructor making `string` a case type
public Union(string? value) { ... }

Als het type union-defining een union member provider is, is elke statische Create methode met één parameter en een retourtype dat identiteits-converteerbaar is naar het union-type zelf een union factory-methode. De casetypen van de samenvoeging worden geïdentificeerd als de set typen die zijn gebouwd op basis van parametertypen van deze factorymethoden op de volgende manier:

  • Als het parametertype een null-type is (of een waarde of verwijzing), is het casetype het onderliggende type
  • Anders is het casetype het parametertype.
// Union factory method making `Cat` a case type
public static Pet Create(Cat value) { ... }
// Union factory method making `int` a case type
public static Union Create(int? value) { ... }
// Union factory method making `string` a case type
public static Union Create(string? value) { ... }

Union constructors en union factory-methoden worden gezamenlijk aangeduid als leden van de verenigingscreatie.

De enkele parameter van een lid voor het maken van een samenvoeging moet een by-value of in parameter zijn.

Een samenvoegingstype moet ten minste één lid van het maken van een unie hebben en daarom ten minste één casetype.

Waardeeigenschap

De Value eigenschap biedt toegang tot de waarde in een samenvoeging, ongeacht het hoofdlettertype.

Elk type samenvoeging moet een Value eigenschap van het type of objectobject? . De eigenschap moet een get toegangsfunctie hebben en optioneel een init of set accessor hebben, die van elke toegankelijkheid kan zijn en niet wordt gebruikt door de compiler.

// Union 'Value' property
public object? Value { get; }

Niet-boksen toegang leden

Een samenvoegingstype kan er ook voor kiezen om het toegangspatroon voor niet-boksende samenvoeging te implementeren, waardoor sterk getypte voorwaardelijke toegang tot elk casetype wordt toegestaan, evenals een manier om te controleren op null.

Hierdoor kan de compiler patroonkoppeling efficiënter implementeren wanneer casetypen waardetypen zijn en als zodanig worden opgeslagen in de samenvoeging.

De leden die geen boksen hebben, zijn:

  • Een HasValue eigenschap van het type bool met een openbare get toegangsbron. Het kan eventueel een init of set accessor hebben, die van elke toegankelijkheid kan zijn en niet wordt gebruikt door de compiler.
  • Een TryGetValue methode voor elk casetype. De methode retourneert bool en neemt één out-parameter van een type dat identiteits-converteerbaar is naar het casetype.
// Non-boxing access members
public bool HasValue { get { ... } }
public bool TryGetValue(out Dog value) { ... }

HasValue wordt verwacht waar te retourneren als en alleen als de samenvoeging Value niet null is.

TryGetValue wordt verwacht waar te retourneren als en alleen als de samenvoeging Value van het opgegeven casetype is. Als dat het geval is, levert u die waarde in de outparameter van de methode.

Welgevormdheid

De taal en compiler maken een aantal gedragsveronderstellingen over samenvoegingstypen. Als een type in aanmerking komt als een samenvoegingstype, maar niet aan deze veronderstellingen voldoet, werkt het gedrag van de samenvoeging mogelijk niet zoals verwacht.

  • Geluidsvermogen: De Value eigenschap evalueert altijd naar null of naar een waarde van een casetype. Dit geldt zelfs voor de standaardwaarde van het samenvoegtype.
  • Stabiliteit: Als een samenvoegwaarde wordt gemaakt op basis van een casetype, komt de Value eigenschap overeen met dat casetype of null. Als een samenvoegwaarde wordt gemaakt op basis van een null waarde, wordt de Value eigenschap .null
  • Gelijkwaardigheid creëren: als een waarde impliciet wordt omgezet in twee verschillende casetypen, heeft het aanmaaklid voor een van deze casetypen hetzelfde waarneembare gedrag wanneer deze met die waarde wordt aangeroepen.
  • Consistentie van toegangspatroon: het gedrag van de HasValue en TryGetValue niet-boksende toegangsleden, indien aanwezig, is waarneembaar gelijk aan die van het rechtstreeks controleren op de Value eigenschap.

Voorbeelden van samenvoegtypen

Pet implementeert het basispatroon van de samenvoeging voor het samenvoegingstype zelf:

[Union] public record struct Pet
{
    // Creation members = case types are 'Dog' and 'Cat'
    public Pet(Dog value) => Value = value;
    public Pet(Cat value) => Value = value;

    // 'Value' property
    public object? Value { get; }
}

IntOrBool implementeert het toegangspatroon voor niet-boksen op het samenvoegtype zelf:

public record struct IntOrBool
{
    private bool _isBool;
    private int _value;

    public IntOrBool(int value) => (_isBool, _value) = (false, value);
    public IntOrBool(bool value) => (_isBool, _value) = (true, value ? 1 : 0);

    public object Value => _isBool ? _value is 1 : _value;

    public bool HasValue => true;
    public bool TryGetValue(out int value)
    {
        value = _value;
        return !_isBool;
    }
    public bool TryGetValue(out bool value)
    {
        value = _isBool && _value is 1;
        return _isBool;
    }
}

Opmerking: Dit is slechts een voorbeeld van hoe het toegangspatroon voor niet-boksen kan worden geïmplementeerd. Met de gebruikerscode kan de inhoud op elke gewenste manier worden opgeslagen. In het bijzonder voorkomt het niet dat de implementatie boksen! De non-boxing naam verwijst naar het toestaan van de patroonkoppeling van de compiler voor toegang tot elk casetype op een sterk getypte manier, in plaats van de object?eigenschap -getypt Value .

Result<T> implementeert het basispatroon via een union member provider:

public record class Result<T> : Result<T>.IUnionMembers
{
    object? _value;

    public interface IUnionMembers
    {
        public static Result<T> Create(T value) => new() { _value = value };
        public static Result<T> Create(Exception value) => new() { _value = value };

        public object? Value { get; }
    }

    object? IUnionMembers.Value => _value;
}

Gedrag van de samenvoeging

Het gedrag van de samenvoeging wordt over het algemeen geïmplementeerd door middel van het basispatroon van de samenvoeging. Als de samenvoeging het niet-boksen toegangspatroon biedt, maakt union pattern matching er bij voorkeur gebruik van.

Samenvoegconversies

Een samenvoegingsconversie wordt impliciet geconverteerd naar een samenvoegtype van elk van de casetypen. Er is met name een samenvoegconversie naar een samenvoegtype U van een type of expressie E als er een standaard impliciete conversie van E naar een type C is en C een parametertype is van een lid Uvan het maken van een samenvoeging. Als samenvoegingstype U een struct is, is er een samenvoegingsconversie van U? een type of expressie E als er een standaard impliciete conversie van E een type naar een type C is en C een parametertype is van een lid Uvan het maken van een samenvoeging.

Een samenvoegconversie is zelf geen standaard impliciete conversie. Het kan daarom niet deelnemen aan een door de gebruiker gedefinieerde impliciete conversie of een andere samenvoegingsconversie.

Er zijn geen expliciete samenvoegconversies buiten de impliciete samenvoegingsconversies. Dus zelfs als er een expliciete conversie is van het casetype Cvan E een samenvoeging, betekent dit niet dat er een expliciete conversie is van E dat type samenvoeging.

Een samenvoegingsconversie wordt uitgevoerd door het lid van de vereniging aan te roepen:

Pet pet = dog;
// becomes
Pet pet = new Pet(dog);
// and
Result<string> result = "Hello"
//becomes
Result<string> result = Result<string>.IUnionMembers.Create("Hello");

Het is een fout als overbelastingsoplossing geen enkel beste kandidaat-lid vindt of als dat lid geen van de leden van de unie is.

Samenvoegingsconversie is slechts een andere 'vorm' van een impliciete door de gebruiker gedefinieerde conversie. Een toepasselijke door de gebruiker gedefinieerde conversieoperator schaduwen.

De logica achter deze beslissing:

Als iemand een door de gebruiker gedefinieerde operator heeft geschreven, moet deze prioriteit krijgen. Met andere woorden, als de gebruiker daadwerkelijk een eigen operator heeft geschreven, willen ze dat we deze noemen. Bestaande typen met conversieoperators die zijn getransformeerd in samenvoegtypen blijven op dezelfde manier werken met betrekking tot bestaande code die de operators momenteel gebruiken.

In het volgende voorbeeld heeft een impliciete door de gebruiker gedefinieerde conversie voorrang op een samenvoegconversie.

struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(int x) => ...
    public S1(string x) => ...
    object System.Runtime.CompilerServices.IUnion.Value => ...
    public static implicit operator S1(int x) => ...
}

class Program
{
    static S1 Test1() => 10; // implicit operator S1(int x) is used
    static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
}

In het volgende voorbeeld, wanneer expliciete cast wordt gebruikt in code, heeft een expliciete door de gebruiker gedefinieerde conversie voorrang op een samenvoegconversie. Maar wanneer er geen expliciete cast-code is, wordt een samenvoegconversie gebruikt omdat expliciete door de gebruiker gedefinieerde conversie niet van toepassing is.

struct S2 : System.Runtime.CompilerServices.IUnion
{
    public S2(int x) => ...
    public S2(string x) => ...
    object System.Runtime.CompilerServices.IUnion.Value => ...
    public static explicit operator S2(int x) => ...
}

class Program
{
    static S2 Test3() => 10; // Union conversion S2.S2(int) is used
    static S2 Test4() => (S2)20; // explicit operator S2(int x)
}

Samenvoeging

Wanneer de binnenkomende waarde van een patroon van een samenvoegingstype of van een nullable van een samenvoegtype is, kunnen de null-waarde en de inhoud van de onderliggende samenvoegwaarde worden 'uitgepakt', afhankelijk van het patroon.

Voor de onvoorwaardelijke _ en var patronen wordt het patroon toegepast op de binnenkomende waarde zelf. Voorbeeld:

if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet`

Alle andere patronen worden echter impliciet toegepast op de eigenschap van Value de onderliggende samenvoeging:

if (GetPet() is Dog dog) { ... }   // 'Dog dog' is applied to 'GetPet().Value'
if (GetPet() is null) { ... }      // 'null' is applied to 'GetPet().Value'
if (GetPet() is { } value) { ... } // '{ } value' is applied to 'GetPet().Value'

Voor logische patronen wordt deze regel afzonderlijk toegepast op de vertakkingen, rekening houdend met het feit dat de linkerbranch van een and patroon van invloed kan zijn op het binnenkomende type van de rechterbranch:

GetPet() switch
{
    var pet and not null   => ... // 'var pet' applies to the incoming 'Pet' and 'not null' to its 'Value'
    not null and var value => ... // 'not null' applies to the 'Value' as does 'var value' because of the 
                                  // left branch changing the incoming type to `object?`.
}

Opmerking: Deze regel betekent dat dit GetPet() is Pet pet waarschijnlijk niet lukt, zoals Pet wordt toegepast op de inhoud, niet op de Pet samenvoeging zelf.

Opmerking: De reden voor de verschillende behandeling van onvoorwaardelijke var patronen (evenals _, wat in wezen een afkorting is voor var _) is een veronderstelling dat hun gebruik kwalitatief anders is dan andere patronen. var patronen worden gewoon gebruikt om de waarde te noemen die wordt vergeleken met, vaak in geneste patronen, zoals PetOwner{ Pet: var pet }. Hier is de handige semantiek bedoeld om pet het samenvoegtype Pette behouden, in plaats van dat de Value eigenschap wordt gededucteerd naar een nutteloos object? type.

Als de binnenkomende waarde een klassetype is, slaagt het null patroon, ongeacht of de samenvoegwaarde zelf of null de ingesloten waarde is null:

if (result is null) { ... } // if (result == null || result.Value == null)

Andere samenvoegingspatronen slagen alleen als de samenvoegwaarde zelf niet nullis.

if (result is 1) { ... } // if (result != null && result.Value is 1)

Als de binnenkomende waarde een type null-waarden is (een struct-samenvoegtype verpakken), slaagt het null patroon, ongeacht of de binnenkomende waarde zelf of null de ingesloten waarde is null:

if (result is null) { ... } // if (result.HasValue == false || result.GetValueOrDefault().Value == null)

Andere samenvoegingspatronen slagen alleen wanneer de binnenkomende waarde zelf niet nullis.

if (result is 1) { ... } // if (result.HasValue && result.GetValueOrDefault().Value is 1)

De compiler geeft de voorkeur aan het implementeren van patroongedrag door middel van leden die zijn voorgeschreven door het niet-boksen toegangspatroon. Hoewel het gratis is om optimalisaties uit te voeren binnen de grenzen van de goed gevormde regels, zijn de volgende minimumsets gegarandeerd van toepassing:

  • Voor een patroon dat het controleren op een specifiek type Timpliceert, als er een TryGetValue(S value) methode beschikbaar is en er een identiteit of impliciete verwijzing/boksconversie van T naar Sis, wordt die methode gebruikt om de waarde te verkrijgen. Het patroon wordt vervolgens toegepast op die waarde. Als er meer dan één dergelijke methode is, heeft elke methode waar de conversie van T naar S is geen boksconversie de voorkeur, indien beschikbaar. Als er nog steeds meer dan één methode is, wordt er een gekozen op een door de implementatie gedefinieerde manier.
  • Anders wordt die eigenschap gebruikt om te controleren of de samenvoegwaarde null is voor een patroon dat impliceert dat wordt gecontroleerd nullop , als er een HasValue eigenschap beschikbaar is.
  • Anders wordt het patroon toegepast op het resultaat van toegang tot de IUnion.Value eigenschap in de binnenkomende samenvoeging.

De operator is-type die wordt toegepast op een samenvoegingstype heeft dezelfde betekenis als een typepatroon dat is toegepast op het samenvoegtype.

Uitputtendheid van de Unie

Een samenvoegtype wordt ervan uitgegaan dat het type 'uitgeput' is door de casetypen. Dit betekent dat een switch expressie volledig is als deze alle casetypen van een samenvoeging verwerkt:

var name = pet switch
{
    Dog dog => ...,
    Cat cat => ...,
    // No warning about non-exhaustive switch
};

Nullbaarheid

De null-status van de eigenschap van een samenvoeging Value wordt bijgehouden zoals elke andere eigenschap, met deze wijzigingen:

  • Wanneer een lid van het maken van een unie wordt aangeroepen (expliciet of via een samenvoegingsconversie), krijgt de nieuwe samenvoeging Value de null-status van de binnenkomende waarde.
  • Wanneer het toegangspatroon van HasValue het niet-boksen is of TryGetValue(...) wordt gebruikt om een query uit te voeren op de inhoud van een samenvoegtype (expliciet of via patroonkoppeling), heeft dit invloed op Valuede null-status op dezelfde manier als wanneer deze Value rechtstreeks is gecontroleerd: de null-status Value wordt 'not null' op de true vertakking.

Zelfs wanneer een schakeloptie voor een samenvoeging anders volledig is, wordt er een waarschuwing gegeven bij niet-verwerkte null als de null-status van de eigenschap van de binnenkomende samenvoeging Value 'misschien null' is.

Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null"
var value = pet switch
{
    Dog dog => ...,
    Cat cat => ...,
    // Warning: 'null' not handled
}

Union-interfaces

De volgende interfaces worden gebruikt door de taal in de implementatie van union-functies.

Interface voor toegang tot union

De IUnion interface markeert een type als samenvoegingstype tijdens het compileren en biedt een manier om tijdens runtime toegang te krijgen tot samenvoeginhoud.

public interface IUnion
{
    // The value of the union or null
    object? Value { get; }
}

Samenvoegingen die door de compiler worden gegenereerd, implementeren deze interface.

Voorbeeldgebruik:

if (value is IUnion { Value: null }) { ... }

Verklaringen van de Unie

Union-declaraties zijn een beknopte en meningswijze manier om samenvoegtypen in C# te declareren. Ze declareren een struct die gebruikmaakt van één objectverwijzing voor het opslaan ervan Value, wat betekent:

  • Boksen: alle waardetypen van hun hoofdletters worden in een vak op de invoer geplaatst.
  • Compactheid: Samenvoegwaarden bevatten slechts één veld.

De bedoeling is dat de declaraties van de unie de overgrote meerderheid van de gebruiksvoorbeelden heel mooi behandelen. De twee belangrijkste redenen voor het handmatig coderen van specifieke union-typen in plaats van gebruik te maken van samenvoegdeclaraties zijn naar verwachting:

  • Bestaande typen aanpassen aan de samenvoegpatronen om samenvoeggedrag te verkrijgen.
  • Het implementeren van een andere opslagstrategie om bijvoorbeeld efficiëntie of interop-redenen.

Syntaxis

Een samenvoegdeclaratie heeft een naam en een lijst met samenvoegconstructietypen .

union_declaration
    : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list?
      '(' type (',' type)* ')'  struct_interfaces? type_parameter_constraints_clause* 
      (`{` struct_member_declaration* `}` | ';')
    ;

Naast de beperkingen voor structleden (§16.3), geldt het volgende voor leden van de unie:

  • Exemplaarvelden, automatische eigenschappen of veldachtige gebeurtenissen zijn niet toegestaan.
  • Expliciet gedeclareerde openbare constructors met één parameter zijn niet toegestaan.
  • Expliciet gedeclareerde constructors moeten een this(...) initialisatiefunctie gebruiken om (direct of indirect) te delegeren aan een van de gegenereerde constructors.

De samenvoegconstructietypen kunnen elk type zijn dat wordt geconverteerd naar object, bijvoorbeeld interfaces, typeparameters, null-typen en andere samenvoegingen. Het is prima voor resulterende gevallen om te overlappen en om samenvoegingen te nesten of null te zijn.

Voorbeelden:

// Union of existing types
public union Pet(Cat, Dog, Bird);

// Union with function member
public union OneOrMore<T>(T, IEnumerable<T>)
{
    public IEnumerable<T> AsEnumerable() => Value switch
    {
        IEnumerable<T> list => list,
        T value => [value],
    }
}

// "Discriminated" union with freshly declared case types
public record class None();
public record class Some<T>(T value);
public union Option<T>(None, Some<T>);

#### Lowering

A union declaration is lowered to a struct declaration with

* the same attributes, modifiers, name, type parameters and constraints,
* implicit implementations of `IUnion`,
* a `public object? Value { get; }` auto-property,
* a public constructor for each *union constructor* type,
* any members in the union declaration's body.

It is an error for user-declared members to conflict with generated members.

Example:

``` c#
public union Pet(Cat, Dog){ ... }

Wordt verlaagd tot:

[Union] public struct Pet : IUnion
{
    public Pet(Cat value) => Value = value;
    public Pet(Dog value) => Value = value;
    
    public object? Value { get; }
    
    ... // original body
}

Open vragen

[Opgelost] Is samenvoeging een record?

Een samenvoegingsdeclaratie wordt verlaagd tot een recordstruct

Ik denk dat dit standaardgedrag onnodig is en, gezien het niet configureerbaar is, zal het gebruiksscenario's aanzienlijk beperken. Records genereren veel code die niet wordt gebruikt of die niet overeenkomen met specifieke vereisten. Records zijn bijvoorbeeld vrijwel verboden in de codebasis van de compiler vanwege die code-bloat. Ik denk dat het beter is om de standaardinstelling te wijzigen:

  • Standaard declareert een samenvoegverklaring een gewone struct met alleen union-specifieke leden.
  • Een gebruiker kan een record-samenvoeging declareren: record union U(E1, ...) ...

Resolutie: Een samenvoegverklaring is een gewone struct, geen record struct. Het record union ... wordt niet ondersteund

[Opgelost] Syntaxis van samenvoegingsdeclaratie

Het lijkt erop dat de voorgestelde syntaxis onvolledig of onnodig beperkt is. Het lijkt er bijvoorbeeld op dat de basiscomponent niet is toegestaan. Ik kan me echter gemakkelijk voorstellen dat een interface moet worden geïmplementeerd, bijvoorbeeld. Ik denk dat, afgezien van de lijst met elementen, de syntaxis moet overeenkomen met de reguliere struct/record struct declaratie waarbij het struct trefwoord wordt vervangen door union trefwoord.

Resolutie: De beperking wordt verwijderd.

[Opgelost] Leden van de Uniedeclaratie

Exemplaarvelden, automatische eigenschappen of veldachtige gebeurtenissen zijn niet toegestaan.

Dit voelt willekeurig en absoluut onnodig.

Resolutie: De beperking wordt gehandhaafd.

[Opgelost] Typen null-waarden als union-casetypen

De casetypen van de samenvoeging worden geïdentificeerd als de set parametertypen van deze constructors. De casetypen van de samenvoeging worden geïdentificeerd als de set parametertypen van deze factorymethoden.

Tegelijkertijd:

Een TryGetValue methode voor elk casetype. De methode retourneert bool en neemt één out-parameter van een type dat overeenkomt met het opgegeven casetype op de volgende manier:

  • Als het casetype een null-waardetype is, moet het type van de parameter id-converteerbaar zijn naar het onderliggende type
  • Anders moet het type identiteit-converteerbaar zijn naar het casetype.

Is er een voordeel dat u een type null-waarde hebt onder de casetypen, met name dat een typepatroon geen null-waardetype kan gebruiken als doeltype? Het lijkt erop dat we gewoon kunnen zeggen dat als het parametertype van de constructor/factory een null-waardetype is, het bijbehorende casetype het onderliggende type is. Dan hebben we die extra component niet nodig voor de TryGetValue methode. Alle outparameters zijn casetypen.

Resolutie: De suggestie wordt goedgekeurd

[Opgelost] Standaard null-status van Value eigenschap

Voor samenvoegtypen waarbij geen van de casetypen null-kan worden gebruikt, is de standaardstatus Value 'not null' in plaats van 'misschien null'.

Met het nieuwe ontwerp, waarbij Value eigenschap niet is gedefinieerd in een algemene interface, maar een API is die specifiek deel uitmaakt van het gedeclareerde type, voelt de bovenstaande regel als over-engineering. Bovendien dwingt de regel gebruikers ertoe om null-typen te gebruiken in situaties waarin anders null-typen niet zouden worden gebruikt.

Denk bijvoorbeeld aan de volgende samenvoegingsdeclaratie:

union U1(int, bool, DateTime);

Volgens de regel voor aan citeren is de standaardstatus Value 'not null'. Maar dat komt niet overeen met het gedrag van het type, default(U1).Value is null. Om het gedrag opnieuw in te stellen, wordt de consument gedwongen ten minste één casetype nullable te maken. Zoiets als:

union U1(int?, bool, DateTime);

Maar dat is waarschijnlijk ongewenst, de consument wil mogelijk geen expliciete creatie met int? waarde toestaan.

Voorstel: Verwijder de regel tussen aanroepen, null-analyse moet aantekeningen van de Value eigenschap gebruiken om de standaard null-waarde ervan af te afleiden.

Resolutie: Het voorstel wordt goedgekeurd

[Opgelost] Samenvoeging voor Nullable van een samenvoegwaardetype

Wanneer de binnenkomende waarde van een patroon van een samenvoegingstype is, kan de inhoud van de samenvoegwaarde 'uitgepakt' zijn, afhankelijk van het patroon.

Moeten we deze regel uitbreiden naar scenario's wanneer binnenkomende waarde van een patroon een Nullable<union type>?

Houd rekening met het volgende scenario:

    static bool Test1(StructUnion? u)
    {
        return u is 1;
    }   

    static bool Test2(ClassUnion? u)
    {
        return u is 1;
    }   

De betekenis van u is 1 Test1 en Test2 is heel anders. In Test1 is het geen samenvoeging, in Test2 is het. Misschien moet 'union matching' 'dig' door Nullable<T> als patroonkoppeling meestal in andere situaties.

Als we dat doen, moet het samenvoegingspatroon null tegen Nullable<union type> klassen werken. Het patroon is waar wanneer (!nullableValue.HasValue || nullableValue.Value.Value is null).

Resolutie: Het voorstel wordt goedgekeurd.

Wat moet ik doen met 'slechte' API's?

Wat moet compiler doen over samenvoegings-API's die eruitzien als een overeenkomst, maar anders 'slecht'? Compiler vindt bijvoorbeeld TryGetValue/HasValue met een overeenkomende handtekening, maar het is 'slecht' omdat een vereiste aangepaste wijziging of een onbekende functie vereist, enzovoort. Moet compiler de API op de achtergrond negeren of een fout melden? Op dezelfde manier kan de API worden gemarkeerd als Verouderd/Experimenteel. Moet compileren diagnostische gegevens melden, op de achtergrond de API gebruiken of de API niet op de achtergrond gebruiken?

Wat als typen voor samenvoegingsdeclaratie ontbreken

Wat gebeurt er als UnionAttribute, IUnion of IUnion<TUnion> ontbreekt? Fout? Synthetiseren? Nog iets anders?

[Opgelost] Ontwerp van algemene IUnion-interface

Er zijn argumenten gemaakt die IUnion<TUnion> niet mogen worden overgenomen van IUnion of de parameter van het type moeten worden beperkt tot IUnion<TUnion>. We moeten teruggaan.

Resolutie: De IUnion<TUnion> interface is voorlopig verwijderd.

[Opgelost] Typen null-waarden als casetypen en hun interactie met TryGetValue

In de bovenstaande regels wordt aangegeven dat als een casetype een null-waardetype is, het parametertype dat in een bijbehorende TryGetValue methode wordt gebruikt, het onderliggende type moet zijn. Dit wordt gemotiveerd door het feit dat een null waarde nooit via deze methode zou worden geretourneerd. Aan de verbruikszijde is een type null-waarde niet toegestaan als een typepatroon, terwijl een overeenkomst met het onderliggende type moet kunnen worden toegewezen aan een aanroep van deze methode.

We moeten bevestigen dat we het eens zijn met deze uitpakking.

Resolutie: Overeengekomen/bevestigd

Het toegangspatroon voor niet-boksen

Moet nauwkeurige regels opgeven voor het vinden van geschikte HasValue api's TryGetValue . Is overname betrokken? Is lezen/schrijven HasValue een acceptabele overeenkomst? Enz.

[Opgelost] TryGetValue overeenkomende conversies

De sectie Union Matching zegt:

Voor een patroon dat impliceert dat wordt gecontroleerd op een specifiek type T, als er een TryGetValue(S value) methode beschikbaar is en er een impliciete conversie van T naar Sis, wordt die methode gebruikt om de waarde te verkrijgen.

Is de set impliciete conversies op welke manier dan ook beperkt? Zijn bijvoorbeeld door de gebruiker gedefinieerde conversies toegestaan? Hoe zit het met tuple-conversies en andere niet zo triviale conversies? Sommige hiervan zijn zelfs standaardconversies.

Is de set TryGetValue methoden op een andere manier beperkt? In de sectie Union Patterns wordt bijvoorbeeld aangegeven dat alleen methoden met een parametertype die overeenkomen met een casetype worden beschouwd:

een public bool TryGetValue(out T value) methode voor elk casetype T.

Het zou goed zijn om een expliciet antwoord te krijgen.

Resolutie: Alleen impliciete identiteit, verwijzings- of boksconversies worden overwogen

TryGetValue en null-analyse

Wanneer het toegangspatroon van HasValue het niet-boksen is of TryGetValue(...) wordt gebruikt om een query uit te voeren op de inhoud van een samenvoegtype (expliciet of via patroonkoppeling), heeft dit invloed op Valuede null-status op dezelfde manier als wanneer deze Value rechtstreeks is gecontroleerd: de null-status Value wordt 'not null' op de true vertakking.

Is de set TryGetValue methoden op welke manier dan ook beperkt? In de sectie Union Patterns wordt bijvoorbeeld aangegeven dat alleen methoden met een parametertype die overeenkomen met een casetype worden beschouwd:

een public bool TryGetValue(out T value) methode voor elk casetype T.

Het zou goed zijn om een expliciet antwoord te krijgen.

Regels rond default waarden van struct-samenvoegingstypen verduidelijken

Opmerking: de standaardregel voor null-baarheid die hieronder wordt vermeld, is verwijderd.

Opmerking: de 'standaard' goed gevormde regels die hieronder worden genoemd, zijn verwijderd. We moeten bevestigen dat dit wat we willen.

Sectie Nullability zegt:

Voor samenvoegtypen waarbij geen van de casetypen null-kan worden gebruikt, is de standaardstatus Value 'not null' in plaats van 'misschien null'.

Aangezien in het onderstaande voorbeeld de huidige implementatie als 'not null' wordt beschouwd Values2 :

S2 s2 = default;

struct S2 : System.Runtime.CompilerServices.IUnion
{
    public S2(int x) => throw null!;
    public S2(bool x) => throw null!;
    object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}

Tegelijkertijd zegt de sectie Well-formness :

  • Standaardwaarde: Als een samenvoegtype een waardetype is, heeft dit de standaardwaarde als null de Valuebijbehorende waarde.
  • Standaardconstructor: Als een samenvoegingstype een nullaire constructor (no-argument) heeft, heeft null de resulterende samenvoeging de Valuebijbehorende .

Een implementatie zoals die in strijd is met het gedrag van null-analyses voor het bovenstaande voorbeeld.

Moeten de goed gevormde regels worden aangepast of moet de status Valuedefault 'misschien null' zijn? Als dit laatste het geval is, moet de initialisatie S2 s2 = default; een waarschuwing voor null-functionaliteit opleveren?

Controleer of een typeparameter nooit een samenvoegingstype is, zelfs niet wanneer deze is beperkt tot één parameter.

class C1 : System.Runtime.CompilerServices.IUnion
{
    private readonly object _value;
    public C1(int x) { _value = x; }
    public C1(string x) { _value = x; }
    object System.Runtime.CompilerServices.IUnion.Value => _value;
}

class Program
{
    static bool Test1<T>(T u) where T : C1
    {
        return u is int; // Not a union matching
    }   

    static bool Test2<T>(T u) where T : C1
    {
        return u is string; // Not a union matching
    }   
}

Moeten kenmerken na voorwaarde van invloed zijn op de standaard null-waarde van een Union-exemplaar?

Opmerking: de standaardregel voor null-baarheid die hieronder wordt vermeld, is verwijderd. En we afleiden niet langer de standaard null-waarde van eigenschap van Value samenvoegingsmethoden. Daarom is de vraag verouderd/niet meer van toepassing op het huidige ontwerp.

Voor samenvoegtypen waarbij geen van de casetypen null-kan worden gebruikt, is de standaardstatus Value 'not null' in plaats van 'misschien null'.

Wordt de waarschuwing verwacht in het volgende scenario

#nullable enable

struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(int x) => throw null!;
    public S1([System.Diagnostics.CodeAnalysis.NotNull] bool? x) => throw null!;
    object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
class Program
{
    static void Test2(S1 s)
    {
       // warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
       //                 For example, the pattern 'null' is not covered.
        _ = s switch { int => 1, bool => 3 }; // 
    } 
}

Samenvoegconversies

[Opgelost] Waar behoren ze tot andere conversies die prioriteitsgericht zijn?

Samenvoegconversies lijken op een andere vorm van een door de gebruiker gedefinieerde conversie. Daarom classificeert de huidige implementatie deze direct na een mislukte poging om een impliciete door de gebruiker gedefinieerde conversie te classificeren en wordt, in het geval van bestaan, beschouwd als een andere vorm van een door de gebruiker gedefinieerde conversie. Dit heeft de volgende gevolgen:

  • Een impliciete door de gebruiker gedefinieerde conversie heeft voorrang op een samenvoegconversie
  • Wanneer expliciete cast wordt gebruikt in code, heeft een expliciete door de gebruiker gedefinieerde conversie voorrang op een samenvoegconversie
  • Wanneer er geen expliciete cast in code is, heeft een samenvoegconversie voorrang op een expliciete door de gebruiker gedefinieerde conversie
struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(int x) => ...
    public S1(string x) => ...
    object System.Runtime.CompilerServices.IUnion.Value => ...
    public static implicit operator S1(int x) => ...
}

struct S2 : System.Runtime.CompilerServices.IUnion
{
    public S2(int x) => ...
    public S2(string x) => ...
    object System.Runtime.CompilerServices.IUnion.Value => ...
    public static explicit operator S2(int x) => ...
}

class Program
{
    static S1 Test1() => 10; // implicit operator S1(int x) is used
    static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
    static S2 Test3() => 10; // Union conversion S2.S2(int) is used
    static S2 Test4() => (S2)20; // explicit operator S2(int x)
}

Moet bevestigen dat dit het gedrag is dat we leuk vinden. Anders moeten de conversieregels worden verduidelijkt.

Resolutie:

Goedgekeurd door de werkgroep.

[Opgelost] Ref-ness van de parameter van de constructor

Op dit moment staat taal alleen op waarde en in parameters toe voor door de gebruiker gedefinieerde conversieoperators. Het lijkt erop dat redenen voor deze beperking ook van toepassing zijn op constructors die geschikt zijn voor samenvoegingsconversies.

Voorstel:

Definitie van een case type constructor sectie Union types hierboven aanpassen:

-For each public constructor with exactly one parameter, the type of that parameter is considered a *case type* of the union type.
+For each public constructor with exactly one **by-value or `in`** parameter, the type of that parameter is considered a *case type* of the union type.

Resolutie:

Goedgekeurd door de werkgroep. We kunnen echter overwegen om de set casetypeconstructors en de set constructors te splitsen die geschikt zijn voor samenvoegingstypeconversies.

[Opgelost] Null-conversies

In de sectie Nullable Conversions worden expliciet conversies vermeld die als onderliggende waarde kunnen worden gebruikt. De huidige specificatie stelt geen aanpassingen voor die lijst voor. Dit resulteert in een fout voor het volgende scenario:

struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(int x) => throw null;
    public S1(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

class Program
{
    static S1? Test1(int x)
    {
        return x; // error CS0029: Cannot implicitly convert type 'int' to 'S1?'
    }   
}

Voorstel:

Pas de specificatie aan ter ondersteuning van een impliciete null-conversie van S naar T? ondersteund door een samenvoegingsconversie. Uitgaande van T een samenvoegingstype is er een impliciete conversie naar een type T? van een type of expressie E als er een samenvoegingsconversie van E naar een type C is en C een casetype is.T Opmerking: er is geen vereiste voor het type E van een niet-null-waardetype. De conversie wordt geëvalueerd als de onderliggende samenvoegconversie van S naar T gevolgd door een terugloop van T naar T?

Resolutie:

Goedgekeurd.

[Opgelost] Lifted conversies

Willen we de sectie Lifted conversions aanpassen om de conversies van lifted unions te ondersteunen? Momenteel zijn ze niet toegestaan:

struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(int x) => throw null;
    public S1(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

class Program
{
    static S1 Test1(int? x)
    {
        return x; // error CS0029: Cannot implicitly convert type 'int?' to 'S1'
    }   

    static S1? Test2(int? y)
    {
        return y; // error CS0029: Cannot implicitly convert type 'int?' to 'S1?'
    }   
}

Resolutie:

Geen opgetilde samenvoegconversies voor nu. Enkele opmerkingen uit de discussie:

De analogie met de door de gebruiker gedefinieerde conversies wordt hier een beetje opgesplitst. In het algemeen kunnen samenvoegingen een null-waarde bevatten die binnenkomt. Het is niet duidelijk of het opheffen een exemplaar van een samenvoegtype moet maken met null de waarde die erin is opgeslagen, of dat er een null waarde van Nullable<Union>moet worden gemaakt.

[Opgelost] Conversie van samenvoeging van een exemplaar van een basistype blokkeren?

Het huidige gedrag kan verwarrend zijn:

struct S1 : System.Runtime.CompilerServices.IUnion
{
    public S1(System.ValueType x)
    {
    }
    public S1(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

class Program
{
    static S1 Test1(System.ValueType x)
    {
        return x; // Union conversion
    }   

    static S1 Test2(System.ValueType y)
    {
        return (S1)y; // Unboxing conversion
    }   
}

Opmerking: taal staat expliciet toe dat door de gebruiker gedefinieerde conversies van een basistype worden gedeclareerde conversies. Daarom kan het ervoor zorgen dat sence om samenvoegingsconversies als dat niet toestaat.

Resolutie:

Doe nu niets speciaals. Algemene scenario's kunnen toch niet volledig worden beveiligd.

[Opgelost] Conversie van samenvoeging van een exemplaar van een interfacetype blokkeren?

Het huidige gedrag kan verwarrend zijn:

struct S1 : I1, System.Runtime.CompilerServices.IUnion
{
    public S1(I1 x) => throw null;
    public S1(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

interface I1 { }

struct S2 : System.Runtime.CompilerServices.IUnion
{
    public S2(I1 x) => throw null;
    public S2(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

class C3 : System.Runtime.CompilerServices.IUnion
{
    public C3(I1 x) => throw null;
    public C3(string x) => throw null;
    object System.Runtime.CompilerServices.IUnion.Value => throw null;
}

class Program
{
    static S1 Test1(I1 x)
    {
        return x; // Union conversion
    }   

    static S1 Test2(I1 x)
    {
        return (S1)x; // Unboxing
    }   

    static S2 Test3(I1 x)
    {
        return x; // Union conversion
    }   

    static S2 Test4(I1 x)
    {
        return (S2)x; // Union conversion
    }   

    static C3 Test3(I1 x)
    {
        return x; // Union conversion
    }   

    static C3 Test4(I1 x)
    {
        return (C3)x; // Reference conversion
    }   
}

Opmerking: taal staat expliciet toe dat door de gebruiker gedefinieerde conversies van een basistype worden gedeclareerde conversies. Daarom kan het ervoor zorgen dat sence om samenvoegingsconversies als dat niet toestaat.

Resolutie:

Doe nu niets speciaals. Algemene scenario's kunnen toch niet volledig worden beveiligd.

Naamruimte van IUnion-interface

De naamruimte voor IUnion de interface blijft niet opgegeven. Als het de bedoeling is om deze in een global naamruimte te houden, geven we dat expliciet aan.

Voorstel: Als dit iets is wat simpelweg over het hoofd wordt gezien, kunnen we naamruimte gebruiken System.Runtime.CompilerServices .

Klassen als Union typen

[Opgelost] Exemplaar zelf controleren op null

Als een samenvoegtype een klassetype is, is het mogelijk dat de waarde null is. Hoe zit het met null-controles dan? Het null patroon is mede gekozen om de Value eigenschap te controleren, dus hoe controleert u of de samenvoeging zelf niet null is?

Voorbeeld:

  • Wanneer S is een Union struct, s is null is voor een waarde S?truealleen wanneer s zichzelf .null Wanneer C is een Union klasse, c is null voor een waarde van C?falsewanneer c zichzelf isnull, maar het is true wanneer c zichzelf niet nullis en c.Value is null.

Nog een voorbeeld:

class C1 : IUnion
{
    private readonly object? _value;

    public C1(){}
    public C1(int x) { _value = x; }
    public C1(string x) { _value = x; }
    object? IUnion.Value => _value;
}

class Program
{
    static int Test1(C1? u)
    {
        // warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
        //                 For example, the pattern 'null' is not covered.
        // This is very confusing, the switch expression is indeed not exhaustive (u itself is not
        // checked for null), but there is a case 'null => 3' in the switch expression. 
        // It looks like the only way to shut off the warning is to use 'case _'. Adding it removes
        // all benefits of exhaustiveness checking, any union case could be missing and there would
        // be no diagnostic about that.  
        return u switch { int => 1, string => 2, null => 3 };
    }
}

Dit deel van het ontwerp is duidelijk geoptimaliseerd rond de verwachting dat een samenvoegtype een struct is. Enkele opties:

  • Jammer. Gebruik == dit voor uw null-controle in plaats van een patroonovereenkomst.
  • Laat het null patroon (en impliciete null-controle in andere patronen) toepassen op zowel de samenvoegwaarde als Value de eigenschap: u is null ==> u == null || u.Value == null
  • Niet toe te staan dat klassen samenvoegtypen zijn.

[Opgelost] Afgeleid van een Union klasse

Wanneer een klasse een Unionklasse als basisklasse gebruikt, wordt deze volgens de huidige specificatie een Unionklasse zelf. Dit gebeurt omdat de implementatie van de interface automatisch wordt overgenomen door de implementatie ervan IUnion . Het is niet vereist om deze opnieuw te implementeren. Tegelijkertijd definiëren constructors van het afgeleide type de set typen in dit nieuwe Union. Het is heel eenvoudig om heel vreemd taalgedrag rond de twee klassen te krijgen:

class C1 : IUnion
{
    private readonly object _value;
    public C1(long x) { _value = x; }
    public C1(string x) { _value = x; }
    object IUnion.Value => _value;
}

class C2(int x) : C1(x);

class Program
{
    static int Test1(C1 u)
    {
        // Good
        return u switch { long => 1, string => 2, null => 3 };
    } 

    static int Test2(C2 u)
    {
        // error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'long'.
        // error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'string'.
        return u switch { long => 1, string => 2, null => 3 };
    } 
}

Enkele opties:

  • Wijzigen wanneer een klassetype een Union type is. Een klasse is bijvoorbeeld een Union type wanneer alles waar is:

    • Dit komt sealed doordat afgeleide typen niet worden beschouwd als Uniontypen, waardoor dit verwarrend is.
    • Geen van de bases implementeert IUnion

    Dit is nog steeds niet perfect. De regels zijn te subtiel. Het is gemakkelijk om een fout te maken. Er is geen diagnose voor de declaratie, maar Union matching werkt niet.

  • Klassen die geen samenvoegingstypen zijn, zijn niet toe te staan.

[Opgelost] De operator is-type

De operator is-type is opgegeven als een runtimetypecontrole. Syntactisch lijkt het erg op een typepatroon, maar het is niet. Daarom wordt de speciale Unionovereenkomst niet gebruikt, wat kan leiden tot verwarring van een gebruiker.

struct S1 : IUnion
{
    private readonly object _value;
    public S1(int x) { _value = x; }
    public S1(string x) { _value = x; }
    object IUnion.Value => _value;
}

class Program
{
    static bool Test1(S1 u)
    {
        return u is int; // warning CS0184: The given expression is never of the provided ('int') type
    }   

    static bool Test2(S1 u)
    {
        return u is string and ['1', .., '2']; // Good
    }   
}

In het geval van een recursieve samenvoeging kan het typepatroon geen waarschuwing geven, maar het doet nog steeds niet wat de gebruiker denkt dat het zou doen.

Resolutie: Moet werken als een typepatroon.

Lijstpatroon

Lijstpatroon mislukt altijd met Union overeenkomende waarden:

struct S1 : IUnion
{
    private readonly object _value;
    public S1(int[] x) { _value = x; }
    public S1(string[] x) { _value = x; }
    object IUnion.Value => _value;
}

class Program
{
    static bool Test1(S1 u)
    {
        // error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found.
        // error CS0021: Cannot apply indexing with [] to an expression of type 'object'
        return u is [10];
    }   
}

static class Extensions
{
    extension(object o)
    {
        public int Length => 0;
    }
}

Andere vragen

  • Zowel het gebruik van constructors in samenvoegingsconversies als het gebruik van TryGetValue(...) in samenvoegpatroonkoppelingen worden opgegeven om lenient te zijn wanneer er meerdere van toepassing zijn: Ze kiezen er slechts één. Dit maakt niet uit volgens de goed gevormde regels, maar zijn we er wel vertrouwd mee?
  • De specificatie is subtly afhankelijk van de implementatie van de IUnion.Value eigenschap in plaats van een Value eigenschap gevonden op het samenvoegtype zelf. Dit is bedoeld om meer flexibiliteit te bieden voor bestaande typen (die mogelijk hun eigen Value eigenschap hebben voor ander gebruik) om het patroon te implementeren. Maar het is onhandig en inconsistent met de manier waarop andere leden worden gevonden en rechtstreeks op het union-type worden gebruikt. Moeten we een wijziging aanbrengen? Enkele andere opties:
    • Samenvoegtypen vereisen om een openbare Value eigenschap beschikbaar te maken.
    • Geef de voorkeur aan een openbare Value eigenschap als deze bestaat, maar als dit niet het gevolg is van de IUnion.Value implementatie (vergelijkbaar met GetEnumerator regels).
  • De voorgestelde syntaxis voor samenvoegingsdeclaratie is niet universeel geliefd, vooral als het gaat om het uitdrukken van de casetypen. Alternatieven tot nu toe ontmoeten ook kritiek, maar het is mogelijk dat we uiteindelijk een verandering aanbrengen. Enkele belangrijke zorgen hebben betrekking op de huidige:
    • Komma's als scheidingstekens tussen hoofdlettertypen lijken te impliceren dat orde van belang is.
    • Lijsten met haakjes zien er te veel uit als primaire constructors (ondanks dat ze geen parameternamen hebben).
    • Te verschillend van opsommingen, die hun 'cases' in accolades hebben.
  • Hoewel samenvoegdeclaraties structs genereren met één verwijzingsveld, zijn ze nog steeds enigszins vatbaar voor onverwacht gedrag wanneer ze in een gelijktijdige context worden gebruikt. Als een door de gebruiker gedefinieerd functielid bijvoorbeeld meerdere keren deductie uitvoert this , kan de met de opgegeven variabele opnieuw zijn toegewezen als geheel door een andere thread tussen de twee toegangen. De compiler kan zo nodig code genereren om naar een lokale te kopiëren this . Moet het? In het algemeen is welke mate van gelijktijdigheidstolerantie wenselijk en redelijkerwijs haalbaar?