Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga / Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są zawarte w odpowiednich notatkach ze spotkania dotyczącego projektowania języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Kwestia dotycząca mistrza: https://github.com/dotnet/csharplang/issues/8677
Podsumowanie
Zezwala na warunkowe przypisanie w wyrażeniu a?.b lub a?[b] .
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();
}
Motywacja
Różne inspirujące przypadki użycia można znaleźć w promowanym temacie. Główne motywacje obejmują:
- Parzystość między właściwościami i
Set()metodami. - Dołączanie programów obsługi zdarzeń w kodzie interfejsu użytkownika.
Szczegółowy projekt
- Prawa strona przypisania jest oceniana tylko wtedy, gdy odbiornik dostępu warunkowego ma wartość inną niż 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();
- Dozwolone są wszystkie formy przypisania złożonego.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Jeśli wynik wyrażenia jest używany, typ wyrażenia musi być znany jako typ wartości lub typ odwołania. Jest to zgodne z istniejącymi zachowaniami dostępu warunkowego.
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
}
- Wyrażenia dostępu warunkowego nadal nie są wartościami lvalue i nadal nie można np. przekazywać do nich
ref.
M(ref a?.b); // error
- Nie można przypisywać referencji do dostępu warunkowego. Głównym powodem jest to, że jedynym sposobem warunkowego uzyskania dostępu do zmiennej ref jest pole ref, a struktury ref są zabronione w przypadku typów wartości dopuszczających wartość null. Jeśli w przyszłości pojawi się prawidłowy scenariusz dla warunkowego przypisania odwołania, możemy wtedy dodać obsługę.
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'.
}
- Nie można np. przypisać dostępów warunkowych za pomocą przypisania przez dekonstrukcję. Przewidujemy, że ludzie rzadko będą chcieli to zrobić, a konieczność robienia tego za pomocą wielu oddzielnych wyrażeń przypisania nie będzie znaczącą wadą.
(a?.b, c?.d) = (x, y); // error
- Operatory inkrementacji/dekrementacji nie są obsługiwane.
a?.b++; // error
--a?.b; // error
- Ta funkcja zazwyczaj nie działa, gdy odbiornik dostępu warunkowego jest typem wartości. Jest to spowodowane tym, że będzie należeć do jednego z następujących dwóch przypadków:
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 proponuje złagodzenie tego problemu, w takim przypadku możemy zdefiniować rozsądne zachowanie dla a?.b = c, gdy a jest System.Nullable<T> i gdy b jest właściwością z ustawieniem tylko do odczytu.
Specyfikacja
Gramatyka przydziału warunkowego o wartości null jest definiowana w następujący sposób:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Zobacz §11.7.7 i §11.7.11 jako odniesienie.
Gdy przypisanie warunkowe z wartością null pojawia się w instrukcji wyrażeniowej, jego semantyka jest następująca:
-
P?.A = Bjest odpowiednikiem parametruif (P is not null) P.A = B;, z tą różnicą, żePjest obliczany tylko raz. -
P?[A] = Bjest odpowiednikiem parametruif (P is not null) P[A] = B, z tą różnicą, żePjest obliczany tylko raz.
W przeciwnym razie jego semantyka jest następująca:
-
P?.A = Bjest odpowiednikiem(P is null) ? (T?)null : (P.A = B), gdzieTjest typem wynikuP.A = B, z tą różnicą, żePjest obliczany tylko raz. -
P?[A] = Bjest odpowiednikiem(P is null) ? (T?)null : (P[A] = B), gdzieTjest typem wynikuP[A] = B, z tą różnicą, żePjest obliczany tylko raz.
Implementacja
Gramatyka w standardzie obecnie nie odpowiada projektowi składni używanemu w implementacji. Oczekujemy, że pozostanie to przypadek po zaimplementowaniu tej funkcji. Projekt składni w implementacji nie powinien faktycznie zmieniać się — tylko sposób jego użycia zmieni się. Przykład:
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
Złożone przykłady
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;
Złe strony
Wybór utrzymania przypisania w ramach dostępu warunkowego wiąże się z dodatkowym nakładem pracy dla IDE, które musi przeanalizować wiele ścieżek kodu, zaczynając od przypisania, aby zidentyfikować przypisywany element.
Alternatywy
Zamiast tego możemy uczynić ?. syntaktycznie elementem podrzędnym =. Każda obsługa wyrażeń = powinna uwzględniać warunkowość prawej strony, jeśli po lewej znajduje się ?.. Sprawia to również, że struktura składni nie odpowiada tak mocno semantyce.
Nierozwiązane pytania
Spotkania dotyczące projektowania
- 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