Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Elle inclut les changements de spécification proposés, ainsi que les informations nécessaires à la conception et au développement de la fonctionnalité. Ces articles sont publiés jusqu'à ce que les changements proposés soient finalisés et incorporés dans la spécification ECMA actuelle.
Il peut y avoir des divergences entre la spécification de la fonctionnalité et l'implémentation réalisée. Ces différences sont capturées dans les notes de réunion de conception de langage (LDM) pertinentes .
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications.
Problème de champion : https://github.com/dotnet/csharplang/issues/8677
Résumé
Autorise l’affectation de manière conditionnelle au sein d’une expression a?.b ou 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();
}
Motivation
Vous trouverez une variété de cas d’usage motivés dans la question défendue. Parmi les principales motivations, citons :
- Parité entre les propriétés et les méthodes
Set(). - Attachement de gestionnaires d’événements dans le code de l’interface utilisateur.
Conception détaillée
- Le côté droit de l'affectation n'est évalué que lorsque le destinataire de l'accès conditionnel n'est pas nul.
// 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();
- Toutes les formes d’affectation composée sont autorisées.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
- Si le résultat de l’expression est utilisé, le type de l’expression doit être connu comme étant d’un type valeur ou d’un type référence. Cela est cohérent avec les comportements existants sur les accès conditionnels.
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
}
- Les expressions d'accès conditionnel ne sont toujours pas des lvalues, et il n'est toujours pas permis, par exemple, de leur appliquer un
ref.
M(ref a?.b); // error
- Il n'est pas autorisé de réaffecter à un accès conditionnel. La raison principale est que la seule façon dont vous pouvez accéder conditionnellement à une variable ref est un champ ref, et les structs ref sont interdits d’être utilisés dans les types de valeurs nullables. Si un scénario valide pour une réaffectation conditionnelle se présentait à l'avenir, nous pourrions ajouter la prise en charge à ce moment-là.
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'.
}
- Il n'est pas possible, par exemple, d'affecter des accès conditionnels via une affectation de déconstruction. Nous prévoyons qu’il sera rare que les gens veuillent le faire, et que ce n'est pas un inconvénient majeur de devoir le faire à travers plusieurs expressions d’affectation distinctes.
(a?.b, c?.d) = (x, y); // error
- Les opérateurs d'incrémentation/décrémentation ne sont pas pris en charge.
a?.b++; // error
--a?.b; // error
- Cette fonctionnalité ne fonctionne généralement pas lorsque le récepteur de l’accès conditionnel est un type valeur. Cela est dû au fait qu’il se trouve dans l’un des deux cas suivants :
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 propose d’assouplir cela, auquel cas nous pourrions définir un comportement raisonnable pour a?.b = c, quand a est un System.Nullable<T> et b est une propriété avec un setter en lecture seule.
Spécification
La grammaire de l’attribution conditionnelle Null est définie comme suit :
null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression
Consultez le §11.7.7 et le §11.7.11 pour référence.
Lorsque l'affectation conditionnelle nulle apparaît dans une instruction-expression, sa sémantique est la suivante :
-
P?.A = Béquivaut àif (P is not null) P.A = B;, sauf qu’elle n’est évaluée qu’unePseule fois. -
P?[A] = Béquivaut àif (P is not null) P[A] = B, sauf qu’elle n’est évaluée qu’unePseule fois.
Sinon, sa sémantique est la suivante :
-
P?.A = Béquivaut à(P is null) ? (T?)null : (P.A = B), oùTest le type de résultat deP.A = B, sauf qu’il n’est évalué qu’unePseule fois. -
P?[A] = Béquivaut à(P is null) ? (T?)null : (P[A] = B), oùTest le type de résultat deP[A] = B, sauf qu’il n’est évalué qu’unePseule fois.
Implémentation
La grammaire dans la norme ne correspond actuellement pas fortement à la conception de syntaxe utilisée dans l’implémentation. Nous nous attendons à ce qu’il reste le cas une fois cette fonctionnalité implémentée. La conception de la syntaxe dans l’implémentation n’est pas censée réellement changer, seule la manière dont elle est utilisée changera. Par exemple:
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
Exemples complexes
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;
Inconvénients
Le choix de conserver l’affectation dans l’accès conditionnel introduit un travail supplémentaire pour l’IDE, qui a de nombreux chemins de code qui doivent fonctionner en arrière d’une affectation pour identifier l’élément affecté.
Alternatives
Nous pourrions plutôt faire de ?. un enfant syntaxique de =. Ainsi, toute gestion des expressions = doit tenir compte de la conditionnalité du côté droit en présence de ?. sur la gauche. Il le fait également de sorte que la structure de la syntaxe ne correspond pas aussi fortement à la sémantique.
Questions non résolues
Concevoir des réunions
- 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