Delen via


Null-voorwaardelijke toewijzing

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/8677

Samenvatting

Hiermee staat u toe dat toewijzing voorwaardelijk binnen een a?.b of a?[b] expressie plaatsvindt.

using System;

class C
{
    public object obj;
}

void M(C? c)
{
    c?.obj = new object();
}
using System;

class C
{
    public event Action E;
}

void M(C? c)
{
    c?.E += () => { Console.WriteLine("handled event E"); };
}
void M(object[]? arr)
{
    arr?[42] = new object();
}

Motivatie

Een verscheidenheid aan motiverende gebruiksvoorbeelden vindt u in het voorvechtende probleem. Belangrijke motivaties zijn onder andere:

  1. Pariteit tussen eigenschappen en Set() methoden.
  2. Gebeurtenis-handlers koppelen in UI-code.

Gedetailleerd ontwerp

  • De rechterkant van de toewijzing wordt alleen geëvalueerd wanneer de ontvanger van de voorwaardelijke toegang niet null is.
// M() is only executed if 'a' is non-null.
// note: the value of 'a.b' doesn't affect whether things are evaluated here.
a?.b = M();
  • Alle vormen van samengestelde toewijzing zijn toegestaan.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
  • Als het resultaat van de expressie wordt gebruikt, moet het type van de expressie bekend zijn dat het een waardetype of een verwijzingstype is. Dit is consistent met bestaand gedrag voor voorwaardelijke toegang.
class C<T>
{
    public T? field;
}

void M1<T>(C<T>? c, T t)
{
    (c?.field = t).ToString(); // error: 'T' cannot be made nullable.
    c?.field = t; // ok
}
  • Expressies voor voorwaardelijke toegang zijn nog steeds geen lvalues en het is nog steeds niet toegestaan om ze te pakken ref .
M(ref a?.b); // error
  • Het is niet toegestaan om opnieuw toe te wijzen aan een voorwaardelijke toegang. De belangrijkste reden hiervoor is dat de enige manier waarop u voorwaardelijk toegang krijgt tot een verw-variabele een verw-veld is en verw-structs niet mogen worden gebruikt in typen null-waarden. Als er in de toekomst een geldig scenario voor een voorwaardelijke verw-toewijzing is opgekomen, kunnen we op dat moment ondersteuning toevoegen.
ref struct RS
{
    public ref int b;
}

void M(RS a, ref int x)
{
  a?.b = ref x; // error: Operator '?' can't be applied to operand of type 'RS'.
}
  • Het is niet mogelijk om bijvoorbeeld toe te wijzen aan voorwaardelijke toegang via de deconstructietoewijzing. We verwachten dat het zeldzaam zal zijn voor mensen om dit te doen, en niet een belangrijk nadeel om dit te doen via meerdere afzonderlijke toewijzingsexpressies in plaats daarvan.
(a?.b, c?.d) = (x, y); // error
a?.b++; // error
--a?.b; // error
  • Deze functie werkt doorgaans niet wanneer de ontvanger van de voorwaardelijke toegang een waardetype is. Dit komt doordat het in een van de volgende twee gevallen valt:
void Case1(MyStruct a)
    => a?.b = c; // a?.b is not allowed when 'a' is of non-nullable value type

void Case2(MyStruct? a)
    => a?.b = c; // `a.Value` is not a variable, so there's no reasonable meaning to define for the assignment

readonly-setter-calls-on-non-variables.md stelt voor dit te ontspannen, in dat geval kunnen we een redelijk gedrag definiëren voor a?.b = c, wanneer a een System.Nullable<T> en b is een eigenschap met een leesbare setter.

Specificatie

De grammatica van de voorwaardelijke toewijzing null wordt als volgt gedefinieerd:

null_conditional_assignment
    : null_conditional_member_access assignment_operator expression
    : null_conditional_element_access assignment_operator expression

Zie §11.7.7 en §11.7.11 ter referentie.

Wanneer de voorwaardelijke toewijzing null wordt weergegeven in een expressie-instructie, zijn de semantiek ervan als volgt:

  • P?.A = B is gelijk aan if (P is not null) P.A = B;, behalve dat wordt P slechts eenmaal geëvalueerd.
  • P?[A] = B is gelijk aan if (P is not null) P[A] = B, behalve dat wordt P slechts eenmaal geëvalueerd.

Anders zijn de semantiek als volgt:

  • P?.A = B is gelijk aan (P is null) ? (T?)null : (P.A = B), waarbij T het resultaattype P.A = Bis , behalve dat dit P slechts eenmaal wordt geëvalueerd.
  • P?[A] = B is gelijk aan (P is null) ? (T?)null : (P[A] = B), waarbij T het resultaattype P[A] = Bis , behalve dat dit P slechts eenmaal wordt geëvalueerd.

Implementatie

De grammatica in de standaard komt momenteel niet sterk overeen met het syntaxisontwerp dat in de implementatie wordt gebruikt. We verwachten dat dit het geval blijft nadat deze functie is geïmplementeerd. Het syntaxisontwerp in de implementatie wordt niet verwacht dat deze daadwerkelijk wordt gewijzigd. Alleen de manier waarop deze wordt gebruikt, wordt gewijzigd. Voorbeeld:

graph TD;
subgraph ConditionalAccessExpression
  whole[a?.b = c]
end
subgraph  
  subgraph WhenNotNull
    whole-->whenNotNull[".b = c"];
    whenNotNull-->.b;
    whenNotNull-->eq[=];
    whenNotNull-->c;
  end
  subgraph OperatorToken
    whole-->?;
  end
  subgraph Expression
    whole-->a;
  end
end

Complexe voorbeelden

class C
{
    ref int M() => /*...*/;
}

void M1(C? c)
{
    c?.M() = 42; // equivalent to:
    if (c is not null)
        c.M() = 42;
}

int? M2(C? c)
{
    return c?.M() = 42; // equivalent to:
    return c is null ? (int?)null : c.M() = 42;
}
M(a?.b?.c = d); // equivalent to:
M(a is null
    ? null
    : (a.b is null
        ? null
        : (a.b.c = d)));
return a?.b = c?.d = e?.f; // equivalent to:
return a?.b = (c?.d = e?.f); // equivalent to:
return a is null
    ? null
    : (a.b = c is null
        ? null
        : (c.d = e is null
            ? null
            : e.f));
}
a?.b ??= c; // equivalent to:
if (a is not null)
{
    if (a.b is null)
    {
        a.b = c;
    }
}

return a?.b ??= c; // equivalent to:
return a is null
    ? null
    : a.b is null
        ? a.b = c
        : a.b;

Nadelen

De keuze om de toewijzing binnen de voorwaardelijke toegang te houden, introduceert wat extra werk voor de IDE, die veel codepaden bevat die terug moeten werken van een toewijzing om het toegewezen punt te identificeren.

Alternatieven

We kunnen in plaats daarvan het ?. syntactisch een kind van de =. Hierdoor moet elke verwerking van = expressies zich bewust worden van de conditionaliteit van de rechterkant in aanwezigheid van ?. aan de linkerkant. Het maakt het ook zo dat de structuur van de syntaxis niet zo sterk overeenkomt met de semantiek.

Niet-opgeloste vragen

Ontwerpbijeenkomsten