Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Anmärkning
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (Language Design Meeting).
Du kan lära dig mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Champion-fråga: https://github.com/dotnet/csharplang/issues/8677
Sammanfattning
Tillåter att tilldelning sker villkorligt inom ett a?.b eller a?[b] ett uttryck.
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();
}
Motivation
En mängd motiverande användningsfall finns i den framhävda frågan. Här är några av de viktigaste motiven:
- Paritet mellan egenskaper och Set()metoder.
- Bifoga händelsehanterare i UI-kod.
Detaljerad design
- Den högra sidan av tilldelningen utvärderas endast när mottagaren av den villkorliga åtkomsten inte är null.
// 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();
- Alla former av sammansatt tilldelning tillåts.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Om resultatet av uttrycket används måste uttryckets typ vara känd för att vara av en värdetyp eller en referenstyp. Detta är förenligt med befintliga beteenden för villkorliga åtkomster.
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
}
- Villkorsstyrda åtkomstuttryck är fortfarande inte lvalues, och det är fortfarande inte tillåtet att t.ex. ta en reftill dem.
M(ref a?.b); // error
- Det är inte tillåtet att återtilldela till en villkorlig åtkomst. Den främsta orsaken till detta är att det enda sättet att villkorligt komma åt en referensvariabel är ett referensfält, och referensstrukturer är förbjudna att användas i nullbara värdetyper. När ett giltigt scenario för en villkorlig referenstilldelning uppstår i framtiden, kan vi lägga till stöd vid den tidpunkten.
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'.
}
- Det går inte att tilldela till villkorliga åtkomster, som t.ex. vid tilldelning via dekonstruktion. Vi förväntar oss att det kommer att vara sällsynt att människor vill göra detta, och att det inte innebär någon betydande nackdel att behöva göra det genom flera separata tilldelningsuttryck.
(a?.b, c?.d) = (x, y); // error
- Operatorer för inkrement/minskning stöds inte.
a?.b++; // error
--a?.b; // error
- Den här funktionen fungerar vanligtvis inte när mottagaren av den villkorliga åtkomsten är en värdetyp. Detta beror på att det kommer att ingå i något av följande två fall:
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 föreslår att lätta på detta krav, i vilket fall vi kan definiera ett rimligt beteende för a?.b = c, när a är en System.Nullable<T> och b är en egenskap med en readonly setter.
Specifikation
Grammatiken för villkorsstyrd tilldelning för null definieras på följande sätt:
null_conditional_assignment
    : null_conditional_member_access assignment_operator expression
    : null_conditional_element_access assignment_operator expression
Se §11.7.7 och §11.7.11 för referens.
När den villkorliga tilldelningen null visas i ett uttrycksuttryck är dess semantik följande:
- 
              P?.A = Bmotsvararif (P is not null) P.A = B;, förutom attPendast utvärderas en gång.
- 
              P?[A] = Bmotsvararif (P is not null) P[A] = B, förutom attPendast utvärderas en gång.
Annars är dess semantik följande:
- 
              P?.A = Bmotsvarar(P is null) ? (T?)null : (P.A = B), därTär resultattypenP.A = Bför , förutom attPendast utvärderas en gång.
- 
              P?[A] = Bmotsvarar(P is null) ? (T?)null : (P[A] = B), därTär resultattypenP[A] = Bför , förutom attPendast utvärderas en gång.
Genomförande
Grammatiken i standarden motsvarar för närvarande inte den syntaxdesign som används i implementeringen. Vi förväntar oss att det förblir fallet när den här funktionen har implementerats. Syntaxdesignen i implementeringen förväntas inte ändras– endast det sätt som den används kommer att ändras. Till exempel:
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
Komplexa exempel
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;
Nackdelar
Valet att behålla tilldelningen inom den villkorliga åtkomsten medför ytterligare arbete för IDE, som har många kodsökvägar som måste arbeta bakåt från en tilldelning för att identifiera det som tilldelas.
Alternativ
Vi kan i stället göra ?. till ett syntaktiskt barn av =. Detta innebär att all hantering av =-uttryck måste ta hänsyn till den villkorliga naturen hos den högra sidan i närvaro av ?. till vänster. Det gör det också så att syntaxens struktur inte motsvarar semantiken lika starkt.
Olösta frågor
Designa möten
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-27.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-26.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-28.md#increment-and-decrement-operators-in-null-conditional-access
C# feature specifications