Ceci est autorisé:
int a, b, c;
a = b = c = 16;
string s = null;
while ((s = "Hello") != null) ;
À ma connaissance, l'affectation s = ”Hello”;
ne doit entraîner qu'une “Hello”
affectation s
, mais l'opération ne doit renvoyer aucune valeur. Si cela était vrai, alors ((s = "Hello") != null)
produirait une erreur, car null
serait comparé à rien.
Quel est le raisonnement derrière l’autorisation des instructions d’affectation à renvoyer une valeur?
while ((s = GetWord()) != null) Process(s);
ne l'est pas).Réponses:
Votre compréhension est incorrecte à 100%. Pouvez-vous expliquer pourquoi vous croyez à cette fausse chose?
Tout d'abord, les instructions d' affectation ne produisent pas de valeur. Les expressions d' affectation produisent une valeur. Une expression d'affectation est une déclaration légale; il n'y a qu'une poignée d'expressions qui sont des déclarations légales en C #: l'attente d'une expression, la construction d'instance, l'incrémentation, la décrémentation, l'invocation et les expressions d'affectation peuvent être utilisées là où une instruction est attendue.
Il n'y a qu'un seul type d'expression en C # qui ne produit pas une sorte de valeur, à savoir, une invocation de quelque chose qui est typé comme retournant void. (Ou, de manière équivalente, une attente d'une tâche sans valeur de résultat associée.) Tout autre type d'expression produit une valeur ou une variable ou une référence ou un accès à une propriété ou un accès à un événement, et ainsi de suite.
Notez que toutes les expressions qui sont légales en tant que déclarations sont utiles pour leurs effets secondaires . C'est le point clé ici, et je pense que c'est peut-être la cause de votre intuition que les affectations devraient être des déclarations et non des expressions. Idéalement, nous aurions exactement un effet secondaire par déclaration, et aucun effet secondaire dans une expression. Il est un peu étrange que du code à effet secondaire puisse être utilisé dans un contexte d'expression.
Le raisonnement derrière l'autorisation de cette fonctionnalité est que (1) elle est souvent pratique et (2) elle est idiomatique dans les langages de type C.
On peut noter que la question a été posée: pourquoi est-ce idiomatique dans les langages de type C?
Dennis Ritchie n'est malheureusement plus disponible pour demander, mais je suppose qu'une affectation laisse presque toujours la valeur qui vient d'être attribuée dans un registre. C est un langage très "proche de la machine". Il semble plausible et conforme à la conception de C qu'il y ait une fonction de langage qui signifie essentiellement "continuez à utiliser la valeur que je viens d'attribuer". Il est très facile d'écrire un générateur de code pour cette fonctionnalité; vous continuez simplement à utiliser le registre qui a stocké la valeur qui a été attribuée.
la source
=
/==
typo, qui est facilement résolue en interdisant l'utilisation de la valeur de=
sauf si elle est entre parenthèses. par exemple,if ((x = y))
ouif ((x = y) == true)
est autorisé maisif (x = y)
ne l'est pas.N'avez-vous pas fourni la réponse? C'est pour activer exactement les types de constructions que vous avez mentionnés.
Un cas courant où cette propriété de l'opérateur d'affectation est utilisée est la lecture de lignes à partir d'un fichier ...
la source
return _myBackingField ?? (_myBackingField = CalculateBackingField());
beaucoup moins de paillettes que de vérifier la valeur null et d'assigner.Mon utilisation préférée des expressions d'affectation est pour les propriétés initialisées paresseusement.
la source
??=
syntaxe.D'une part, il vous permet d'enchaîner vos affectations, comme dans votre exemple:
Pour un autre, il vous permet d'assigner et de vérifier un résultat dans une seule expression:
Les deux sont peut-être des raisons douteuses, mais il y a certainement des gens qui aiment ces constructions.
la source
return (HttpContext.Current.Items["x"] = myvar);
Outre les raisons déjà mentionnées (chaînage des affectations, set-and-test dans les boucles while, etc.), pour utiliser correctement l'
using
instruction, vous avez besoin de cette fonctionnalité:MSDN déconseille de déclarer l'objet jetable en dehors de l'instruction using, car il restera alors dans la portée même après avoir été supprimé (voir l'article MSDN que j'ai lié).
la source
using
. Tous ces éléments sont légaux:using (X x = new X())
,using (x = new X())
,using (x)
. Cependant, dans cet exemple, le contenu de l'instruction using est une syntaxe spéciale qui ne repose pas du tout sur l'affectation renvoyant une valeur -Font font3 = new Font("Arial", 10.0f)
n'est pas une expression et n'est valide à aucun endroit qui attend des expressions.Je voudrais développer un point précis qu'Eric Lippert a soulevé dans sa réponse et mettre en lumière une occasion particulière qui n'a été du tout évoquée par personne d'autre. Eric a dit:
Je voudrais dire que l'affectation laissera toujours derrière la valeur que nous avons essayé d'attribuer à notre opérande gauche. Pas seulement «presque toujours». Mais je ne sais pas car je n'ai pas trouvé ce problème commenté dans la documentation. Cela pourrait théoriquement être une procédure mise en œuvre très efficace pour «laisser derrière» et ne pas réévaluer l'opérande gauche, mais est-ce efficace?
«Efficace» oui pour tous les exemples construits jusqu'à présent dans les réponses de ce fil. Mais efficace dans le cas des propriétés et des indexeurs qui utilisent des accesseurs get et set? Pas du tout. Considérez ce code:
Ici, nous avons une propriété, qui n'est même pas un wrapper pour une variable privée. Chaque fois qu'il sera appelé, il retournera vrai, chaque fois que l'on essayera de fixer sa valeur, il ne fera rien. Ainsi, chaque fois que cette propriété est évaluée, il sera véridique. Voyons ce qui se passe:
Devinez ce qu'il imprime? Il imprime
Unexpected!!
. En fait, l'accesseur set est en effet appelé, ce qui ne fait rien. Mais par la suite, l'accesseur get n'est jamais appelé du tout. L'affectation laisse simplement derrière lafalse
valeur que nous avons essayé d'attribuer à notre propriété. Et cettefalse
valeur est ce que l'instruction if évalue.Je terminerai par un exemple concret qui m'a amené à faire des recherches sur ce problème. J'ai créé un indexeur qui était un wrapper pratique pour une collection (
List<string>
) qu'une de mes classes avait comme variable privée.Le paramètre envoyé à l'indexeur était une chaîne, qui devait être traitée comme une valeur dans ma collection. L'accesseur get renverrait simplement true ou false si cette valeur existait ou non dans la liste. Ainsi, l'accesseur get était une autre façon d'utiliser la
List<T>.Contains
méthode.Si l'accesseur set de l'indexeur était appelé avec une chaîne comme argument et que l'opérande de droite était un booléen
true
, il ajouterait ce paramètre à la liste. Mais si le même paramètre était envoyé à l'accesseur et que l'opérande de droite était un booléenfalse
, il supprimerait à la place l'élément de la liste. Ainsi, l'accesseur set a été utilisé comme une alternative pratique à la foisList<T>.Add
etList<T>.Remove
.Je pensais avoir une "API" soignée et compacte enveloppant la liste avec ma propre logique implémentée en tant que passerelle. Avec l'aide d'un indexeur seul, je pouvais faire beaucoup de choses avec quelques combinaisons de touches. Par exemple, comment puis-je essayer d'ajouter une valeur à ma liste et vérifier qu'elle s'y trouve? Je pensais que c'était la seule ligne de code nécessaire:
Mais comme mon exemple précédent l'a montré, l'accesseur get qui est censé voir si la valeur est vraiment dans la liste n'a même pas été appelé. La
true
valeur était toujours laissée pour détruire efficacement la logique que j'avais implémentée dans mon accesseur get.la source
Si l'affectation ne retournait pas de valeur, la ligne
a = b = c = 16
ne fonctionnerait pas non plus.Etre capable d'écrire des choses comme
while ((s = readLine()) != null)
peut parfois être utile.Donc, la raison pour laquelle l'affectation renvoie la valeur assignée est de vous laisser faire ces choses.
la source
=
se produire dans une expression qui n'est pas entre parenthèses (sans compter les parenthèses de l'instruction if / while elle-même). gcc donne de tels avertissements et a ainsi essentiellement éliminé cette classe de bogues des programmes C / C ++ compilés avec lui. C'est dommage que d'autres auteurs de compilateurs aient prêté si peu d'attention à cela et à plusieurs autres bonnes idées dans gcc.Je pense que vous ne comprenez pas comment l'analyseur va interpréter cette syntaxe. L'affectation sera évaluée en premier , et le résultat sera ensuite comparé à NULL, c'est-à-dire que l'instruction équivaut à:
Comme d'autres l'ont souligné, le résultat d'une affectation est la valeur attribuée. J'ai du mal à imaginer l'avantage d'avoir
((s = "Hello") != null)
et
pas être équivalent ...
la source
Je pense que la raison principale est la similitude (intentionnelle) avec C ++ et C.Faire en sorte que l'opérateur d'assignation (et beaucoup d'autres constructions de langage) se comportent comme leurs homologues C ++ suit simplement le principe de la moindre surprise, et tout programmeur venant d'un autre bouclé- le langage entre parenthèses peut les utiliser sans trop y penser. Être facile à comprendre pour les programmeurs C ++ était l'un des principaux objectifs de conception pour C #.
la source
Pour les deux raisons que vous incluez dans votre message
1) vous pouvez donc faire
a = b = c = 16
2) afin que vous puissiez tester si un devoir a réussi
if ((s = openSomeHandle()) != null)
la source
Le fait que 'a ++' ou 'printf ("foo")' puisse être utile soit en tant qu'instruction autonome soit en tant que partie d'une expression plus large signifie que C doit permettre la possibilité que les résultats de l'expression soient ou non utilisé. Compte tenu de cela, il existe une notion générale selon laquelle les expressions qui pourraient utilement «renvoyer» une valeur peuvent aussi bien le faire. Le chaînage des affectations peut être légèrement "intéressant" en C, et encore plus intéressant en C ++, si toutes les variables en question n'ont pas précisément le même type. Il vaut probablement mieux éviter de tels usages.
la source
Un autre excellent exemple d'utilisation, j'utilise ceci tout le temps:
la source
Un avantage supplémentaire que je ne vois pas dans les réponses ici, est que la syntaxe de l'affectation est basée sur l'arithmétique.
Désormais, cela
x = y = b = c = 2 + 3
signifie quelque chose de différent en arithmétique d'un langage de style C; en arithmétique, c'est une assertion, nous déclarons que x est égal à y etc. et dans un langage de style C, c'est une instruction qui rend x égal à y etc. après son exécution.Cela dit, il y a encore suffisamment de relations entre l'arithmétique et le code pour qu'il n'y ait pas de sens d'interdire ce qui est naturel dans l'arithmétique à moins qu'il n'y ait une bonne raison. (L'autre chose que les langages de style C ont tirée de l'utilisation du symbole égal est l'utilisation de == pour la comparaison d'égalité. Ici, cependant, parce que le plus à droite == renvoie une valeur, ce type de chaînage serait impossible.)
la source
a = b = c
signifie quea
etb
etc
sont la même chose. Lors de l'apprentissage d'un langage de style C, nous apprenons cela aprèsa = b = c
,a
etb
c'est la même chose quec
. Il y a certainement une différence de sémantique, comme le dit ma réponse elle-même, mais quand j'étais enfant, j'ai appris pour la première fois à programmer dans une langue qui utilisait=
pour les devoirs mais qui ne le permettait pas,a = b = c
cela me semblait déraisonnable, cependant…=
pour la comparaison d'égalité, donc celaa = b = c
devrait signifier ce quea = b == c
signifie les langages de style C. J'ai trouvé le chaînage autorisé en C beaucoup plus intuitif car je pouvais faire une analogie avec l'arithmétique.J'aime utiliser la valeur de retour d'affectation lorsque j'ai besoin de mettre à jour un tas de choses et de retourner s'il y a eu ou non des changements:
Soyez prudent cependant. Vous pourriez penser que vous pouvez le raccourcir à ceci:
Mais cela arrêtera en fait d'évaluer les instructions ou après avoir trouvé la première vraie. Dans ce cas, cela signifie qu'il arrête d'attribuer les valeurs suivantes une fois qu'il affecte la première valeur différente.
Voir https://dotnetfiddle.net/e05Rh8 pour jouer avec ça
la source