Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Nota:
Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.
Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de idioma (LDM) pertinentes.
Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones.
Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/8677
Resumen
Permite que la asignación se produzca condicionalmente dentro de una a?.b expresión 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();
}
Motivación
Se puede encontrar una variedad de casos de uso motivadores en el asunto promovido. Entre las motivaciones principales se incluyen:
- Paridad entre propiedades y
Set()métodos. - Adjuntar controladores de eventos en el código de la interfaz de usuario.
Diseño detallado
- El lado derecho de la asignación solo se evalúa cuando el receptor del acceso condicional no es 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();
- Se permiten todas las formas de asignación compuesta.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Si se usa el resultado de la expresión, se debe saber que el tipo de la expresión es de un tipo de valor o de un tipo de referencia. Esto es coherente con los comportamientos existentes en los accesos condicionales.
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
}
- Las expresiones de acceso condicional siguen sin ser lvalues y sigue sin estar permitido, por ejemplo, tomar una
refa ellas.
M(ref a?.b); // error
- No se permite asignar referencias a un acceso condicional. La razón principal de esto es que la única manera de acceder condicionalmente a una variable ref es un campo ref y las estructuras ref están prohibidas de usarse en tipos de valor que aceptan valores NULL. Si en el futuro surgiera un escenario válido para una asignación de referencia condicional, podríamos añadir soporte en ese 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'.
}
- No es posible asignar accesos condicionales a través de la asignación de deconstrucción. Prevemos que será poco frecuente que las personas quieran hacerlo, y que no es una desventaja significativa tener que hacerlo, en cambio, en varias expresiones de asignación independientes.
(a?.b, c?.d) = (x, y); // error
a?.b++; // error
--a?.b; // error
- Esta característica generalmente no funciona cuando el destinatario del acceso condicional es un tipo de valor. Esto se debe a que caerá en uno de los dos casos siguientes:
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 relajar esto, en cuyo caso podríamos definir un comportamiento razonable para a?.b = c, cuando a es un System.Nullable<T> y b es una propiedad con un establecedor de solo lectura.
Especificación
La gramática de asignación condicional nula se define de la siguiente manera:
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Consulte §11.7.7 y §11.7.11 como referencia.
Cuando la asignación condicional nula aparece en una expresión-instrucción, su semántica es la siguiente.
-
P?.A = Bes equivalente aif (P is not null) P.A = B;, excepto quePsolo se evalúa una vez. -
P?[A] = Bes equivalente aif (P is not null) P[A] = B, excepto quePsolo se evalúa una vez.
De lo contrario, su semántica es la siguiente:
-
P?.A = Bes equivalente a(P is null) ? (T?)null : (P.A = B), dondeTes el tipo de resultado deP.A = B, excepto quePsolo se evalúa una vez. -
P?[A] = Bes equivalente a(P is null) ? (T?)null : (P[A] = B), dondeTes el tipo de resultado deP[A] = B, excepto quePsolo se evalúa una vez.
Implementación
Actualmente, la gramática del estándar no se corresponde con el diseño de sintaxis usado en la implementación. Esperamos que siga siendo el caso después de implementar esta característica. No se espera que el diseño de la sintaxis de la implementación cambie realmente; solo cambiará la forma en que se usa. Por ejemplo:
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
Ejemplos complejos
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;
Inconvenientes
La opción de mantener la asignación dentro del acceso condicional presenta algún trabajo adicional para el IDE, que tiene muchas rutas de acceso de código que necesitan retroceder desde una asignación para identificar lo que se asigna.
Alternativas
En su lugar, podríamos hacer que ?. sea sintácticamente un hijo del =. Esto hace que cualquier manejo de expresiones = sea consciente de la condicionalidad del lado derecho en presencia de ?. a la izquierda. También hace que la estructura de la sintaxis no se corresponda tan fuertemente con la semántica.
Preguntas sin resolver
Reuniones de diseño
- 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