Pourquoi la déclaration (j ++); interdit?

169

Le code suivant est erroné (voir sur ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

erreur CS0201: Seules les expressions d'affectation, d'appel, d'incrémentation, de décrémentation, d'attente et de nouvel objet peuvent être utilisées comme instruction

  1. Pourquoi le code se compile-t-il lorsque nous supprimons les parenthèses?
  2. Pourquoi ne compile-t-il pas avec les parenthèses?
  3. Pourquoi le C # a-t-il été conçu de cette façon?
utilisateur10607
la source
29
@Servy beaucoup de personnes impliquées dans la conception du langage C # sont sur SO, c'est pourquoi j'ai demandé. Quelque chose ne va pas avec ça?
user10607
34
Je ne peux pas imaginer une question plus sur le sujet que "pourquoi un langage de programmation spécifique gère-t-il ce comportement spécifique de cette manière?"
Mage Xy
20
Nous avons maintenant une réponse d'Eric Lippert. Vous voulez peut-être repenser votre décision concernant la réponse acceptée. C'est Noël, alors peut-être avez-vous accepté la réponse trop tôt.
Thomas Weller
17
@Servy Voulez-vous dire que les décisions de conception ne sont jamais documentées et sont absolument inconnues de tous les autres? Il ne demande pas aux utilisateurs de SO de deviner, il demande une réponse - cela n'implique pas qu'il demande une estimation. Que les gens répondent par une supposition est sur eux , pas sur lui. Et comme OP l'a souligné, il y a des gens sur le débordement de pile qui ont réellement travaillé sur C # et ont pris ces décisions.
Rob
5
@Rob: Le problème est que, oui, à moins que la justification ne soit déjà documentée quelque part, elle le doit, mais la spéculation est amusante et qui ne veut pas partager la sienne? Si vous trouvez un moyen de vous assurer que ces suppositions pures (ou "éclairées") sont correctement éliminées, dites-le-moi et nous ferons fortune.
Deduplicator

Réponses:

221

Des aperçus profonds appréciés.

Je ferai de mon mieux.

Comme d'autres réponses l'ont noté, ce qui se passe ici, c'est que le compilateur détecte qu'une expression est utilisée comme instruction . Dans de nombreux langages - C, JavaScript et bien d'autres - il est parfaitement légal d'utiliser une expression comme une déclaration. 2 + 2;est légal dans ces langues, même s'il s'agit d'une déclaration sans effet. Certaines expressions ne sont utiles que pour leurs valeurs, certaines expressions ne sont utiles que pour leurs effets secondaires (comme un appel à une méthode de retour void) et certaines expressions, malheureusement, sont utiles pour les deux. (Comme incrément.)

Le fait est que les déclarations qui consistent uniquement en expressions sont presque certainement des erreurs à moins que ces expressions ne soient généralement considérées comme plus utiles pour leurs effets secondaires que leurs valeurs . Les concepteurs C # souhaitaient trouver un terrain d'entente, en autorisant les expressions généralement considérées comme ayant un effet secondaire, tout en refusant celles qui sont également généralement considérées comme utiles pour leurs valeurs. L'ensemble d'expressions qu'ils ont identifié dans C # 1.0 étaient des incréments, des décrémentations, des appels de méthode, des affectations et, de manière quelque peu controversée, des invocations de constructeur.


A part: On pense normalement qu'une construction d'objet est utilisée pour la valeur qu'elle produit, pas pour l'effet secondaire de la construction; à mon avis, permettre new Foo();est un peu une erreur. En particulier, j'ai vu ce modèle dans le code du monde réel qui a causé un défaut de sécurité:

catch(FooException ex) { new BarException(ex); } 

Il peut être étonnamment difficile de repérer ce défaut si le code est compliqué.


Le compilateur fonctionne donc pour détecter toutes les instructions qui consistent en des expressions qui ne figurent pas sur cette liste. En particulier, les expressions entre parenthèses sont identifiées comme cela - des expressions entre parenthèses. Ils ne figurent pas dans la liste des «expressions autorisées en tant qu'expressions d'instruction», donc elles sont interdites.

Tout cela est au service d'un principe de conception du langage C #. Si vous avez tapé, (x++);vous avez probablement fait quelque chose de mal . C'est probablement une faute de frappe M(x++);ou quelque chose de juste. N'oubliez pas que l'attitude de l'équipe du compilateur C # n'est pas " pouvons-nous trouver un moyen de faire ce travail? " L'attitude de l'équipe du compilateur C # est " si le code plausible ressemble à une erreur probable, informons le développeur ". Les développeurs C # aiment cette attitude.

Maintenant, tout ce que dit, il y a effectivement quelques cas bizarres où la spécification C # n'Évoquez ou énoncez pur et simple que les parenthèses ne sont pas autorisés , mais le compilateur C # leur permet de toute façon. Dans presque tous ces cas, la différence mineure entre le comportement spécifié et le comportement autorisé est totalement inoffensive, de sorte que les auteurs du compilateur n'ont jamais corrigé ces petits bogues. Vous pouvez en savoir plus ici:

Y a-t-il une différence entre return myVar et return (myVar)?

Eric Lippert
la source
5
Re: "On pense normalement à un appel de constructeur comme étant utilisé pour la valeur qu'il produit, pas pour l'effet secondaire de la construction; à mon avis, c'est un peu une erreur": j'imagine qu'un facteur dans cette décision était que si le compilateur interdit, il n'y aurait pas de solution propre dans le cas où vous êtes appelez pour un effet secondaire. (La plupart des autres déclarations d'expressions interdites ont des parties étrangères qui ne servent à rien et peuvent être supprimées, à l'exception de ... ? ... : ..., où le correctif est d'utiliser if/ à la elseplace.)
ruakh
1
@ruakh: Puisque c'est un comportement qui devrait être découragé, un correctif propre n'est pas nécessaire, tant qu'il existe un correctif raisonnablement bon marché / simple. Ce qu'il y a; affectez-le à une variable et n'utilisez pas cette variable. Si vous voulez être flagrant que vous ne l'utilisez pas, donnez à cette ligne de code sa propre portée. Alors que techniquement on pourrait soutenir que cela change la sémantique du code (une affectation de référence inutile est toujours une affectation de référence inutile), en pratique, il est peu probable que cela ait une importance. J'avoue qu'il génère des IL légèrement différents ... mais seulement si compilé sans optimisations.
Brian
Safari, Firefox et Chrome donnent tous ReferenceError lors de la saisie contineu;.
Brian McCutchon
2
@McBrainy: Vous avez raison. Je me souviens mal du défaut qui résulte de la faute d'orthographe; Je vais vérifier mes notes. En attendant, j'ai supprimé la déclaration incriminée car elle n'est pas pertinente ici.
Eric Lippert
1
@Mike: Je suis d'accord. Une autre situation dans laquelle nous voyons parfois l'instruction est celle des cas de test, try { new Foo(null); } catch (ArgumentNullException)...mais évidemment, ces situations ne sont pas par définition du code de production. Il semble raisonnable qu'un tel code puisse être écrit pour être affecté à une variable factice.
Eric Lippert
46

Dans la spécification du langage C #

Les instructions d'expression sont utilisées pour évaluer les expressions. Les expressions qui peuvent être utilisées comme instructions incluent les appels de méthode, les allocations d'objets à l'aide du nouvel opérateur, les affectations utilisant = et les opérateurs d'affectation composés, les opérations d'incrémentation et de décrémentation à l'aide des opérateurs ++ et - et des expressions d'attente.

Le fait de placer des parenthèses autour d'une instruction crée une nouvelle expression dite entre parenthèses. De la spécification:

Une expression entre parenthèses consiste en une expression entre parenthèses. ... Une expression entre parenthèses est évaluée en évaluant l'expression entre parenthèses. Si l'expression entre parenthèses désigne un espace de noms ou un type, une erreur de compilation se produit. Sinon, le résultat de l'expression entre parenthèses est le résultat de l'évaluation de l'expression contenue.

Étant donné que les expressions entre parenthèses ne sont pas répertoriées comme une instruction d'expression valide, il ne s'agit pas d'une instruction valide selon la spécification. La raison pour laquelle les concepteurs ont choisi de le faire de cette façon est une hypothèse, mais mon pari est que les parenthèses ne font aucun travail utile si la déclaration entière est contenue entre parenthèses: stmtet (stmt)sont exactement les mêmes.

Frank Bryce
la source
4
@Servy: Si vous lisez attentivement, c'était un peu plus nuancé que cela. La soi-disant «déclaration d'expression» est en effet une déclaration valide, et la spécification répertorie les types d'expressions qui peuvent être des déclarations valides. Cependant, je répète à partir de la spécification que le type d'expression appelé «expression entre parenthèses» n'est PAS sur la liste des expressions valides qui peuvent être utilisées comme déclarations d'expression valides.
Frank Bryce
4
Le OPne demande rien sur la conception du langage. Il veut juste savoir pourquoi c'est une erreur. La réponse est: parce que ce n'est pas une déclaration valide .
Leandro
9
OK, mon mal. La réponse est: parce que, selon la spécification , ce n'est pas une déclaration valide. Voilà: une raison de conception !
Leandro
3
La question n'est pas de demander une raison profonde et axée sur la conception pour expliquer pourquoi le langage a été conçu pour gérer cette syntaxe de cette manière - il se demande pourquoi un morceau de code se compile alors qu'un autre morceau de code (qui pour les débutants semble devoir se comporter de manière identique) ne parvient pas à se compiler. Cet article répond à cela.
Mage Xy
4
@Servy JohnCarpenter ne se contente pas de dire "Vous ne pouvez pas faire ça". Il dit que ces deux choses qui vous confondaient ne sont pas les mêmes. j ++ est une instruction d'expression, tandis que (j ++) est une expression entre parenthèses. Tout à coup, l'OP connaît maintenant la différence et ce qu'elle est. C'est une bonne réponse. Il répond au corps de sa question et non au titre. Je pense que beaucoup de discorde vient du mot «conception» dans le titre de la question, mais cela n'a pas à être répondu par les concepteurs, juste glanés dans les spécifications.
thinklarge
19

parce que les crochets autour de i++créent / définissent une expression .. comme le message d'erreur l'indique .. une expression simple ne peut pas être utilisée comme instruction.

pourquoi le langage a été conçu pour être ainsi? pour éviter les bogues, ayant des expressions trompeuses comme instructions, qui ne produisent aucun effet secondaire comme avoir le code

int j = 5;
j+1; 

la deuxième ligne n'a aucun effet (mais vous ne l'avez peut-être pas remarqué). Mais au lieu que le compilateur le supprime (car le code n'est pas nécessaire), il vous demande explicitement de le supprimer (vous serez donc au courant de l'erreur) OU de le corriger au cas où vous auriez oublié de taper quelque chose.

modifier :

pour rendre la partie sur les crochets plus claire .. les crochets en c # (en plus d'autres utilisations, comme le cast et l'appel de fonction), sont utilisés pour regrouper des expressions et renvoyer une seule expression (créer les sous-expressions).

à ce niveau de code, seuls les staments sont autorisés.

j++; is a valid statement because it produces side effects

mais en utilisant le crochet, vous le transformez en expression

myTempExpression = (j++)

et ça

myTempExpression;

n'est pas valide car le compilateur ne peut pas garantir que l'expression est un effet secondaire (non sans entraîner le problème d'arrêt).

CaldasGSM
la source
Ouais, c'est ce que j'ai pensé au début aussi. Mais cela n'ont un effet secondaire. Il effectue un post-incrémentation de la variable, non? j
user10607
1
j++;est une déclaration valide, mais en utilisant les crochets, vous dites let me take this stement and turn it into an expression.. et les expressions ne sont pas valides à ce stade du code
CaldasGSM
C'est dommage qu'il n'y ait pas de forme d'instruction "calculer et ignorer", car il y a des occasions où le code peut souhaiter affirmer qu'une valeur peut être calculée sans lever d'exception, mais sans se soucier de la valeur réelle ainsi calculée. Une instruction compute-and-ignore ne serait pas très souvent utilisée, mais rendrait clairement l'intention du programmeur dans de tels cas.
supercat du