Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas LDM (reunião de design de idioma) pertinentes.
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações.
Problema do especialista: https://github.com/dotnet/csharplang/issues/8677
Resumo
Permite que a atribuição ocorra condicionalmente dentro de uma a?.b expressão or 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();
}
Motivação
Uma variedade de casos de uso motivadores pode ser encontrada na questão defendida. As principais motivações incluem:
- Paridade entre propriedades e
Set()métodos. - Anexando manipuladores de eventos no código da interface do usuário.
Design detalhado
- O lado direito da atribuição só é avaliado quando o receptor do acesso condicional não é nulo.
// 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();
- Todas as formas de atribuição composta são permitidas.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Se o resultado da expressão for usado, o tipo da expressão deverá ser conhecido como sendo de um tipo de valor ou de um tipo de referência. Isso é consistente com os comportamentos existentes em acessos condicionais.
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
}
- As expressões de acesso condicional ainda não são lvalues e ainda não é permitido, por exemplo, levar a
refelas.
M(ref a?.b); // error
- Não é permitido atribuir ref a um acesso condicional. A principal razão para isso é que a única maneira de acessar condicionalmente uma variável ref é um campo ref, e ref structs são proibidos de serem usados em tipos de valor anuláveis. Se um cenário válido para uma atribuição de ref condicional surgir no futuro, poderemos adicionar suporte naquele 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'.
}
- Não é possível, por exemplo, atribuir a acessos condicionais por meio da atribuição de desconstrução. Prevemos que será raro que as pessoas queiram fazer isso, e não é uma desvantagem significativa precisar fazê-lo em várias expressões de atribuição separadas.
(a?.b, c?.d) = (x, y); // error
- Não há suporte para operadores de incremento/decremento.
a?.b++; // error
--a?.b; // error
- Esse recurso geralmente não funciona quando o receptor do acesso condicional é um tipo de valor. Isso ocorre porque ele se enquadrará em um dos dois seguintes casos:
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 propõe relaxar isso, nesse caso, poderíamos definir um comportamento razoável para a?.b = c, quando a é um System.Nullable<T> e b é uma propriedade com um setter readonly.
Especificação
A gramática de atribuição condicional nula é definida da seguinte maneira:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Consulte §11.7.7 e §11.7.11 para referência.
Quando a atribuição condicional nula aparece em uma instrução de expressão, sua semântica é a seguinte:
-
P?.A = Bé equivalente aif (P is not null) P.A = B;, exceto quePé avaliado apenas uma vez. -
P?[A] = Bé equivalente aif (P is not null) P[A] = B, exceto quePé avaliado apenas uma vez.
Caso contrário, sua semântica é a seguinte:
-
P?.A = Bé equivalente a(P is null) ? (T?)null : (P.A = B), ondeTé o tipo de resultado deP.A = B, exceto quePé avaliado apenas uma vez. -
P?[A] = Bé equivalente a(P is null) ? (T?)null : (P[A] = B), ondeTé o tipo de resultado deP[A] = B, exceto quePé avaliado apenas uma vez.
Implementação
A gramática no padrão atualmente não corresponde fortemente ao design de sintaxe usado na implementação. Esperamos que continue assim depois que esse recurso for implementado. Não se espera que o design da sintaxe na implementação mude de fato, apenas a maneira como ele é usado mudará. Por exemplo:
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
Exemplos complexos
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;
Desvantagens
A opção de manter a atribuição dentro do acesso condicional introduz algum trabalho adicional para o IDE, que tem muitos caminhos de código que precisam funcionar de trás para frente de uma atribuição para identificar a coisa que está sendo atribuída.
Alternativas
Em vez disso, poderíamos tornar o ?. sintaticamente um filho do =. Isso faz com que qualquer manipulação de = expressões precise se conscientizar da condicionalidade do lado direito na presença de ?. à esquerda. Também faz com que a estrutura da sintaxe não corresponda tão fortemente à semântica.
Perguntas não resolvidas
Reuniões de 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