Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).
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 .
Edição campeã: https://github.com/dotnet/csharplang/issues/8677
Resumo
Permite que a atribuição ocorra condicionalmente dentro de uma a?.b
ou a?[b]
expressão.
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 recetor 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 deve ser conhecido como sendo de um tipo de valor ou 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, levá-las
ref
a.
M(ref a?.b); // error
- Não é permitido recusar atribuir 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 ref condicional surgisse no futuro, poderíamos 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 acessos condicionais através da atribuição de desconstrução. Prevemos que será raro as pessoas quererem fazer isso, e não será 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/decréscimo.
a?.b++; // error
--a?.b; // error
- Esse recurso geralmente não funciona quando o recetor do acesso condicional é um tipo de valor. Isso porque ele se enquadrará em um dos dois casos a seguir:
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, caso em que poderíamos definir um comportamento razoável para a?.b = c
, quando a
é um System.Nullable<T>
e b
é uma propriedade com um setter somente leitura.
Especificação
A gramática de atribuição condicional nula é definida da seguinte forma:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Ver referência nos pontos 11.7.7 e 11.7.11 .
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.
Execução
A gramática no padrão atualmente não corresponde fortemente ao design de sintaxe usado na implementação. Esperamos que continue a ser assim depois de esta funcionalidade ser implementada. Não se espera que o design da sintaxe na implementação realmente mude, apenas a maneira como ela é usada 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 trabalhar para trás de uma atribuição para identificar a coisa que está sendo atribuída.
Alternativas
Poderíamos, em vez disso, fazer do ?.
sintaticamente um filho do =
. Isso faz com que qualquer manipulação de =
expressões precise tomar consciência da condicionalidade do lado direito na presença do ?.
lado esquerdo. Isso também faz com que a estrutura da sintaxe não corresponda tão fortemente à semântica.
Questões por resolver
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