Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den relevanten Sprachentwurfsbesprechungen (LDM)-Notizen erfasst.
Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/8677
Zusammenfassung
Ermöglicht die Zuweisung bedingt innerhalb eines a?.b oder a?[b] Ausdrucks.
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
Eine Vielzahl motivierender Anwendungsfälle finden Sie in der Problemfrage. Zu den wichtigsten Motivationen gehören:
- Parität zwischen Eigenschaften und
Set()Methoden. - Anfügen von Ereignishandlern im UI-Code.
Detailliertes Design
- Die rechte Seite der Zuweisung wird nur ausgewertet, wenn der Empfänger des bedingten Zugriffs nicht Null ist.
// 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 Formen der zusammengesetzten Zuordnung sind zulässig.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Wenn das Ergebnis des Ausdrucks verwendet wird, muss bekannt sein, dass der Typ des Ausdrucks ein Werttyp oder Verweistyp ist. Dies entspricht den vorhandenen Verhaltensweisen für bedingten Zugriff.
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
}
- Bedingte Zugriffsausdrücke sind immer noch keine L-Werte und es ist immer noch nicht zulässig, ihnen beispielsweise einen
refzuweisen.
M(ref a?.b); // error
- Es ist nicht zulässig, eine Referenzzuweisung an einen bedingten Zugriff vorzunehmen. Der Hauptgrund hierfür ist, dass der einzige Zugriff auf eine ref-Variable nur über ein ref-Feld erfolgen kann, und ref-Strukturen dürfen in nullable Wertetypen nicht verwendet werden. Wenn in Zukunft ein gültiges Szenario für eine bedingte Verweiszuweisung auftritt, könnten wir zu diesem Zeitpunkt Unterstützung hinzufügen.
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'.
}
- Es ist z.B. nicht möglich, bedingten Zugriffen durch Destrukturzuweisung zuzuweisen. Wir gehen davon aus, dass es selten vorkommen wird, dass Menschen dies tun möchten, und dass es kein bedeutender Nachteil ist, was stattdessen erfordert, dies über mehrere separate Zuweisungsausdrücke zu tun.
(a?.b, c?.d) = (x, y); // error
- Inkrement-/Dekrementoperatoren werden nicht unterstützt.
a?.b++; // error
--a?.b; // error
- Dieses Feature funktioniert in der Regel nicht, wenn der Empfänger des bedingten Zugriffs ein Werttyp ist. Dies liegt daran, dass sie in einen der folgenden beiden Fälle fällt:
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 schlägt vor, dies zu lockern. In diesem Fall könnten wir ein sinnvolles Verhalten für a?.b = c definieren, wenn a ein System.Nullable<T> ist und b eine Eigenschaft mit einem schreibgeschützten Setter ist.
Spezifikation
Die Grammatik für die nullbedingte Zuweisung ist wie folgt definiert:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Referenz finden Sie unter §11.7.7 und §11.7.11 .
Wenn die bedingte NULL-Zuordnung in einer Ausdrucksanweisung vorkommt, ist die Semantik wie folgt:
-
P?.A = Bist gleichbedeutend mitif (P is not null) P.A = B;der Ausnahme, dassPnur einmal ausgewertet wird. -
P?[A] = Bist gleichbedeutend mitif (P is not null) P[A] = Bder Ausnahme, dassPnur einmal ausgewertet wird.
Andernfalls ist die Semantik wie folgt:
-
P?.A = Bist äquivalent zu(P is null) ? (T?)null : (P.A = B), wobeiTder Ergebnistyp vonP.A = Bist, mit der Ausnahme, dassPnur einmal ausgewertet wird. -
P?[A] = Bist äquivalent zu(P is null) ? (T?)null : (P[A] = B), wobeiTder Ergebnistyp vonP[A] = Bist, mit der Ausnahme, dassPnur einmal ausgewertet wird.
Implementierung
Die Grammatik im Standard entspricht derzeit nicht stark dem syntaxdesign, das in der Implementierung verwendet wird. Wir erwarten, dass dies nach der Implementierung dieses Features weiterhin der Fall sein wird. Der Syntaxentwurf in der Implementierung wird nicht erwartet, dass er sich tatsächlich ändert – nur die Art und Weise, wie sie verwendet wird, ändert sich. Beispiel:
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
Komplexe Beispiele
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;
Nachteile
Die Wahl, die Zuweisung im bedingten Zugriff beizubehalten, führt einige zusätzliche Arbeit für die IDE ein, die viele Codepfade enthält, die von einer Aufgabe rückwärts arbeiten müssen, um das zugewiesene Objekt zu identifizieren.
Alternativen
Wir könnten stattdessen die ?. syntaktisch als ein Kind von = machen. Dies erfordert, dass die Behandlung von =-Ausdrücken die Bedingtheit der rechten Seite beachten muss, wenn ?. auf der linken Seite vorhanden ist. Sie macht es auch so, dass die Struktur der Syntax nicht so stark der Semantik entspricht.
Ungelöste Fragen
Planungsbesprechungen
- 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