Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .
Ulteriori dettagli sul processo di adozione delle specifiche di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .
Questione prioritaria: https://github.com/dotnet/csharplang/issues/8677
Riassunto
Consente l'assegnazione in modo condizionale all'interno di un'espressione a?.b o 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();
}
Motivazione
Un'ampia gamma di casi d'uso motivanti si possono trovare nel problema sostenuto. Le motivazioni principali includono:
- Parità tra proprietà e
Set()metodi. - Collegamento di gestori eventi nel codice dell'interfaccia utente.
Progettazione dettagliata
- La parte destra dell'assegnazione viene valutata solo quando il ricevitore dell'accesso condizionale è diverso da 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();
- Sono consentite tutte le forme di assegnazione composta.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Se viene usato il risultato dell'espressione, il tipo dell'espressione deve essere noto come di un tipo valore o di un tipo riferimento. Questo comportamento è coerente con i comportamenti esistenti sugli accessi condizionali.
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
}
- Le espressioni di accesso condizionale non sono ancora gli lvalue e non è ancora consentito, ad esempio, applicare un'operazione
refsu di esse.
M(ref a?.b); // error
- Non è consentito assegnare riferimenti a un accesso condizionale. Il motivo principale è che l'unico modo per accedere in modo condizionale a una variabile ref è un campo ref e gli struct ref non possono essere usati nei tipi valore nullable. Se in futuro si presentasse uno scenario valido per un'assegnazione di riferimento condizionale, potremmo aggiungere il supporto in quel momento.
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'.
}
- Non è possibile, ad esempio, effettuare assegnazioni sugli accessi condizionali tramite la decostruzione. Si prevede che sarà raro che qualcuno voglia farlo e non sarà un notevole svantaggio doverlo fare su più espressioni di assegnazione separate.
(a?.b, c?.d) = (x, y); // error
- Gli operatori di incremento/decremento non sono supportati.
a?.b++; // error
--a?.b; // error
- Questa funzionalità in genere non funziona quando il ricevitore dell'accesso condizionale è un tipo di valore. Ciò è dovuto al fatto che rientra in uno dei due casi seguenti:
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 propone di rilassarsi, nel qual caso potremmo definire un comportamento ragionevole per a?.b = c, quando a è un System.Nullable<T> e b è una proprietà con un setter di sola lettura.
Specificazione
La grammatica dell'assegnazione condizionale Null è definita come segue:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Per informazioni di riferimento, vedere §11.7.7 e §11.7.11 .
Quando l'assegnazione condizionale Null appare in un'istruzione-espressione, la sua semantica è la seguente:
-
P?.A = Bè equivalente aif (P is not null) P.A = B;, ad eccezione del fatto chePviene valutato una sola volta. -
P?[A] = Bè equivalente aif (P is not null) P[A] = B, ad eccezione del fatto chePviene valutato una sola volta.
In caso contrario, la semantica è la seguente:
-
P?.A = Bequivale a(P is null) ? (T?)null : (P.A = B), doveTè il tipo di risultato di , ad eccezione delP.A = Bfatto chePviene valutato una sola volta. -
P?[A] = Bequivale a(P is null) ? (T?)null : (P[A] = B), doveTè il tipo di risultato di , ad eccezione delP[A] = Bfatto chePviene valutato una sola volta.
Implementazione
La grammatica nello standard attualmente non corrisponde fortemente alla progettazione della sintassi usata nell'implementazione. Ci aspettiamo che rimanga il caso dopo l'implementazione di questa funzionalità. La progettazione della sintassi nell'implementazione non dovrebbe effettivamente cambiare, ma solo il modo in cui viene usato cambierà. Per esempio:
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
Esempi complessi
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;
Svantaggi
La scelta di mantenere l'assegnazione all'interno dell'accesso condizionale introduce alcune operazioni aggiuntive per l'IDE, che include molti percorsi di codice che devono funzionare all'indietro da un'assegnazione per identificare l'elemento assegnato.
Le alternative
È invece possibile rendere sintatticamente un ?. figlio dell'oggetto =. In questo modo, qualsiasi gestione delle = espressioni deve essere consapevole dell'condizionalità del lato destro in presenza di ?. a sinistra. Fa sì che la struttura della sintassi non corrisponda altrettanto fortemente alla semantica.
Domande non risolte
Incontri di design
- 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