Perché era la dichiarazione (j ++); vietato?


169

Il codice seguente è errato (vederlo su ideone ):

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

errore CS0201: Solo un'assegnazione, una chiamata, un incremento, un decremento, un attesa e nuove espressioni oggetto possono essere utilizzate come un'istruzione

  1. Perché il codice viene compilato quando rimuoviamo le parentesi?
  2. Perché non si compila con le parentesi?
  3. Perché C # è stato progettato in questo modo?

29
@Servy molte persone coinvolte nella progettazione del linguaggio C # sono su SO, quindi è per questo che ho chiesto. Qualcosa non va in questo?
user10607

34
Non riesco a immaginare una domanda più sull'argomento di "perché un linguaggio di programmazione specifico gestisce questo comportamento specifico in questo modo?"
Mago Xy,

20
Ora abbiamo una risposta di Eric Lippert. Forse vuoi ripensare la tua decisione sulla risposta accettata. È Natale, quindi forse hai accettato la risposta troppo presto.
Thomas Weller,

17
@Servy Stai insinuando che le decisioni di progettazione non sono mai documentate e sono assolutamente sconosciute a tutti gli altri? Lui non chiede utenti in modo da indovinare, che sta chiedendo una risposta - che non implica che sta chiedendo per una supposizione. Se la gente risponde con un'ipotesi è su di loro , non su di lui. E come ha sottolineato OP, ci sono persone in overflow dello stack che hanno effettivamente lavorato su C # e hanno preso queste decisioni.
Rob

5
@Rob: Il problema è che, sì, a meno che la logica non sia già documentata da qualche parte, deve, ma la speculazione è divertente e chi non vuole condividere la propria? Se trovi un modo per assicurarti che tali ipotesi pure (o "istruite") siano abbattute in modo affidabile, dimmelo e faremo una fortuna.
Deduplicatore il

Risposte:


221

Approfondimenti approfonditi apprezzati.

Farò del mio meglio.

Come hanno notato altre risposte, quello che sta succedendo qui è che il compilatore sta rilevando che un'espressione viene utilizzata come un'istruzione . In molte lingue - C, JavaScript e molte altre - è perfettamente legale usare un'espressione come una dichiarazione. 2 + 2;è legale in queste lingue, anche se questa è un'affermazione che non ha alcun effetto. Alcune espressioni sono utili solo per i loro valori, alcune espressioni sono utili solo per i loro effetti collaterali (come una chiamata a un metodo di ritorno vuoto) e alcune espressioni, purtroppo, sono utili per entrambi. (Come incremento.)

Punto centrale: le affermazioni che consistono solo di espressioni sono quasi certamente errori a meno che quelle espressioni non siano in genere considerate più utili per i loro effetti collaterali rispetto ai loro valori . I progettisti di C # desideravano trovare una via di mezzo, consentendo espressioni che erano generalmente pensate come effetti collaterali, mentre non consentivano quelle che sono anche generalmente considerate utili per i loro valori. L'insieme di espressioni che hanno identificato in C # 1.0 erano incrementi, decrementi, chiamate a metodi, assegnazioni e in qualche modo controverse invocazioni di costruttori.


A PARTIRE: normalmente si pensa a una costruzione di oggetti utilizzata per il valore che produce, non per l'effetto collaterale della costruzione; a mio avviso, consentire new Foo();è un po 'un malfunzionamento. In particolare, ho visto questo modello nel codice del mondo reale che ha causato un difetto di sicurezza:

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

Può essere sorprendentemente difficile individuare questo difetto se il codice è complicato.


Il compilatore quindi lavora per rilevare tutte le istruzioni che consistono in espressioni che non sono in quell'elenco. In particolare, le espressioni tra parentesi sono identificate proprio come espressioni tra parentesi. Non sono nell'elenco di "consentiti come espressioni di istruzione", quindi non sono consentiti.

Tutto ciò è al servizio di un principio progettuale del linguaggio C #. Se hai digitato (x++);probabilmente stavi facendo qualcosa di sbagliato . Questo è probabilmente un refuso per M(x++);o qualche cosa giusta. Ricorda, l'atteggiamento del team del compilatore C # non è " possiamo capire in che modo farlo funzionare? " L'atteggiamento del team del compilatore C # è " se il codice plausibile sembra un probabile errore, informiamo lo sviluppatore ". Agli sviluppatori C # piace questo atteggiamento.

Ora, tutto ciò che ha detto, in realtà ci sono alcuni casi in cui il dispari # specifica C non implicano o statali e tondo che tra parentesi non sono consentiti, ma il compilatore C # li permette comunque. In quasi tutti quei casi la piccola discrepanza tra il comportamento specificato e il comportamento consentito è completamente innocua, quindi gli autori del compilatore non hanno mai risolto questi piccoli bug. Puoi leggere su quelli qui:

C'è una differenza tra return myVar e return (myVar)?


5
Ri: "Normalmente si pensa a un'invocazione del costruttore usata per il valore che produce, non per l'effetto collaterale della costruzione; a mio avviso si tratta di un malfunzionamento": immagino che un fattore in questa decisione era che se il compilatore lo proibiva, non ci sarebbe alcuna correzione pulita nei casi in cui lo si chiama per un effetto collaterale. (La maggior parte delle altre dichiarazioni di espressioni proibite hanno parti estranee che non servono a nulla e possono essere rimosse, ad eccezione di ... ? ... : ...dove la correzione deve essere utilizzata if/ elseinvece.)
ruakh

1
@ruakh: poiché si tratta di un comportamento che dovrebbe essere scoraggiato, non è necessaria una correzione pulita, purché esista una correzione ragionevolmente economica / semplice. Quale c'è; assegnarlo a una variabile e non usare quella variabile. Se vuoi essere palese che non lo stai usando, dai a quella riga di codice il suo ambito. Mentre tecnicamente si potrebbe sostenere che questo cambia la semantica del codice (un'assegnazione di riferimento inutile è ancora un'assegnazione di riferimento inutile), in pratica è improbabile che importi. Ammetto che genera IL leggermente diverso ... ma solo se compilato senza ottimizzazioni.
Brian,

Safari, Firefox e Chrome forniscono tutti ReferenceError durante la digitazione contineu;.
Brian McCutchon,

2
@McBrainy: hai ragione. Non ricordo il difetto derivante dall'ortografia errata; Controllerò i miei appunti. Nel frattempo ho eliminato l'affermazione offensiva in quanto non è pertinente al punto più ampio qui.
Eric Lippert,

1
@Mike: sono d'accordo. Un'altra situazione in cui a volte vediamo l'istruzione sono casi di test come, try { new Foo(null); } catch (ArgumentNullException)...ma ovviamente queste situazioni non sono per definizione codice di produzione. Sembra ragionevole che tale codice possa essere scritto da assegnare a una variabile fittizia.
Eric Lippert,

46

Nelle specifiche del linguaggio C #

Le dichiarazioni di espressione vengono utilizzate per valutare le espressioni. Le espressioni che possono essere usate come istruzioni includono invocazioni di metodi, allocazioni di oggetti usando il nuovo operatore, assegnazioni usando = e gli operatori di assegnazione composti, operazioni di incremento e decremento usando gli operatori ++ e - e attendo espressioni.

Mettere le parentesi attorno a un'istruzione crea una nuova cosiddetta espressione tra parentesi. Dalla specifica:

Un'espressione tra parentesi è costituita da un'espressione racchiusa tra parentesi. ... Un'espressione tra parentesi viene valutata valutando l'espressione tra parentesi. Se l'espressione tra parentesi indica uno spazio dei nomi o un tipo, si verifica un errore in fase di compilazione. Altrimenti, il risultato dell'espressione tra parentesi è il risultato della valutazione dell'espressione contenuta.

Poiché le espressioni tra parentesi non sono elencate come una dichiarazione di espressione valida, non è una dichiarazione valida secondo la specifica. Il motivo per cui i progettisti hanno scelto di farlo in questo modo è un'ipotesi di chiunque, ma la mia scommessa è perché le parentesi non fanno un lavoro utile se l'intera affermazione è racchiusa tra parentesi: stmte (stmt)sono esattamente le stesse.


4
@Servy: se leggi da vicino, era leggermente più sfumato di così. La cosiddetta "Dichiarazione di espressione" è in effetti una dichiarazione valida e la specifica elenca i tipi di espressioni che possono essere dichiarazioni valide. Tuttavia, ripeto dalle specifiche che il tipo di espressione chiamato "espressione tra parentesi" NON è nell'elenco delle espressioni valide che possono essere utilizzate come istruzioni di espressione valide.
Frank Bryce,

4
Non OPsi chiede nulla riguardo al design del linguaggio. Vuole solo sapere perché questo è un errore. La risposta è: perché non è una dichiarazione valida .
Leandro,

9
OK, mio ​​male. La risposta è: perché, secondo le specifiche , non è una dichiarazione valida. Ecco qua: un motivo di design !
Leandro,

3
La domanda non è chiedere una ragione profonda, guidata dal design, del perché il linguaggio sia stato progettato per gestire questa sintassi in questo modo - si sta chiedendo perché un pezzo di codice si compili quando un altro pezzo di codice (che ai principianti sembra che dovrebbe comportarsi identicamente) non riesce a compilare. Questo post risponde a questo.
Mago Xy,

4
@Servy JohnCarpenter non sta solo dicendo "Non puoi farlo". Sta dicendo che quelle due cose da cui sei stato confuso non sono le stesse. j ++ è un'istruzione di espressione, mentre (j ++) è un'espressione tra parentesi. All'improvviso l'OP ora conosce la differenza e quali sono. Questa è una buona risposta Risponde al corpo della sua domanda, non al titolo. Penso che molta contesa derivi dalla parola "design" nel titolo della domanda, ma ai progettisti non deve essere data risposta, ma solo dalle specifiche.
thinklarge

19

perché le parentesi attorno a i++stanno creando / definendo un'espressione .. come dice il messaggio di errore .. una semplice espressione non può essere usata come un'istruzione.

perché la lingua è stata progettata per essere così? per prevenire i bug, che hanno espressioni fuorvianti come istruzioni, che non producono alcun effetto collaterale come avere il codice

int j = 5;
j+1; 

la seconda riga non ha alcun effetto (ma potresti non averlo notato). Ma invece di rimuoverlo dal compilatore (perché il codice non è necessario) .it ti chiede esplicitamente di rimuoverlo (quindi sarai a conoscenza o l'errore) O correggilo nel caso in cui ti sei dimenticato di digitare qualcosa.

modifica :

per rendere più chiara la parte relativa alle parentesi quadre .. le parentesi quadre in c # (oltre ad altri usi, come cast e call di funzione), vengono utilizzate per raggruppare espressioni e restituire una singola espressione (creare espressioni secondarie).

a quel livello di codice sono ammessi solo gli stament .. così

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

ma usando il bracketing lo stai trasformando in espressione

myTempExpression = (j++)

e questo

myTempExpression;

non è valido perché il compilatore non può garantire che l'espressione come effetto collaterale (non senza incorrere nel problema di arresto).


Sì, è quello che ho pensato anche all'inizio. Ma questo ha un effetto collaterale. Esegue post-incremento della jvariabile giusto?
user10607

1
j++;è una dichiarazione valida, ma usando le parentesi che stai dicendo let me take this stement and turn it into an expression.. e le espressioni non sono valide a quel punto nel codice
CaldasGSM

È un peccato che non vi sia alcuna forma di istruzione "calcola e ignora", poiché ci sono occasioni in cui il codice potrebbe voler affermare che un valore può essere calcolato senza generare un'eccezione, ma non preoccuparsi del valore reale così calcolato. Un'istruzione compute-and-ignore non sarebbe usata terribilmente spesso, ma renderebbe chiara l'intenzione del programmatore in questi casi.
supercat il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.