Warum war die Aussage (j ++); verboten?

169

Der folgende Code ist falsch (siehe ideone ):

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

Fehler CS0201: Nur Anweisungen für Zuweisung, Aufruf, Inkrementieren, Dekrementieren, Warten und neue Objekte können als Anweisung verwendet werden

  1. Warum wird der Code kompiliert, wenn wir die Klammern entfernen?
  2. Warum wird nicht mit den Klammern kompiliert?
  3. Warum wurde C # so entworfen?
user10607
quelle
29
@Servy Viele Leute, die mit C # -Sprachendesign zu tun haben, sind auf SO, deshalb habe ich gefragt. Stimmt etwas damit nicht?
user10607
34
Ich kann mir keine themenbezogenere Frage vorstellen als "Warum behandelt eine bestimmte Programmiersprache dieses bestimmte Verhalten auf diese Weise?"
Magier Xy
20
Jetzt haben wir eine Antwort von Eric Lippert. Vielleicht möchten Sie Ihre Entscheidung über die akzeptierte Antwort überdenken. Es ist Weihnachten, vielleicht haben Sie die Antwort zu früh angenommen.
Thomas Weller
17
@Servy Wollen Sie damit sagen, dass Entwurfsentscheidungen niemals dokumentiert werden und allen anderen absolut unbekannt sind? Er bittet SO-Benutzer nicht um eine Vermutung, er bittet um eine Antwort - das bedeutet nicht, dass er um eine Vermutung bittet. Ob die Leute mit einer Vermutung antworten, liegt bei ihnen , nicht bei ihm. Und wie OP betonte, gibt es Leute mit Stapelüberlauf, die tatsächlich an C # gearbeitet und diese Entscheidungen getroffen haben.
Rob
5
@Rob: Das Problem ist, dass es ja sein muss, es sei denn, die Begründung ist bereits irgendwo dokumentiert, aber Spekulation macht Spaß und wer möchte nicht seine eigenen teilen? Wenn Sie einen Weg finden, um sicherzustellen, dass solche reinen (oder "gebildeten") Vermutungen zuverlässig ausgesondert werden, sagen Sie es mir und wir werden ein Vermögen machen.
Deduplikator

Antworten:

221

Tiefe Einsichten geschätzt.

Ich werde mein Bestes geben.

Wie andere Antworten angemerkt haben, erkennt der Compiler hier, dass ein Ausdruck als Anweisung verwendet wird . In vielen Sprachen - C, JavaScript und vielen anderen - ist es völlig legal, einen Ausdruck als Aussage zu verwenden. 2 + 2;ist in diesen Sprachen legal, obwohl dies eine Aussage ist, die keine Wirkung hat. Einige Ausdrücke sind nur für ihre Werte nützlich, einige Ausdrücke sind nur für ihre Nebenwirkungen nützlich (z. B. ein Aufruf einer Methode zur Rückgabe von Leeren) und einige Ausdrücke sind leider für beide nützlich. (Wie Inkrement.)

Punkt ist: Aussagen, die nur aus Ausdrücken bestehen, sind mit ziemlicher Sicherheit Fehler, es sei denn, diese Ausdrücke werden normalerweise als nützlicher für ihre Nebenwirkungen angesehen als ihre Werte . C # -Designer wollten einen Mittelweg finden, indem sie Ausdrücke zuließen, die allgemein als Nebenwirkungen angesehen wurden, während diejenigen, die normalerweise auch als nützlich für ihre Werte angesehen werden, nicht zugelassen wurden. Die in C # 1.0 identifizierten Ausdrücke waren Inkremente, Dekremente, Methodenaufrufe, Zuweisungen und etwas kontrovers diskutierte Konstruktoraufrufe.


ASIDE: Normalerweise wird eine Objektkonstruktion als für den Wert verwendet angesehen, den sie erzeugt, nicht für den Nebeneffekt der Konstruktion. Meiner Meinung nach ist das Zulassen new Foo();eine Fehlfunktion. Insbesondere habe ich dieses Muster im realen Code gesehen, der einen Sicherheitsmangel verursacht hat:

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

Es kann überraschend schwierig sein, diesen Fehler zu erkennen, wenn der Code kompliziert ist.


Der Compiler erkennt daher alle Anweisungen, die aus Ausdrücken bestehen, die nicht in dieser Liste enthalten sind. Insbesondere werden Ausdrücke in Klammern als genau das identifiziert - Ausdrücke in Klammern. Sie sind nicht auf der Liste der "als Anweisungsausdrücke zugelassen" aufgeführt, daher sind sie nicht zulässig.

All dies dient einem Entwurfsprinzip der C # -Sprache. Wenn Sie getippt haben, haben (x++);Sie wahrscheinlich etwas falsch gemacht . Dies ist wahrscheinlich ein Tippfehler für M(x++);oder eine gerechte Sache. Denken Sie daran, dass die Haltung des C # -Compilerteams nicht lautet: " Können wir einen Weg finden, wie dies funktioniert? " Die Haltung des C # -Compilerteams lautet: " Wenn plausibler Code wie ein wahrscheinlicher Fehler aussieht, informieren wir den Entwickler. " C # -Entwickler mögen diese Einstellung.

Nun, alles in allem gibt es tatsächlich einige seltsame Fälle, in denen die C # -Spezifikation impliziert oder direkt besagt , dass Klammern nicht zulässig sind, der C # -Compiler sie jedoch trotzdem zulässt. In fast allen diesen Fällen ist die geringfügige Diskrepanz zwischen dem angegebenen Verhalten und dem zulässigen Verhalten völlig harmlos, sodass die Compiler-Autoren diese kleinen Fehler nie behoben haben. Über diese können Sie hier lesen:

Gibt es einen Unterschied zwischen return myVar und return (myVar)?

Eric Lippert
quelle
5
Betreff: "Man denkt normalerweise, dass ein Konstruktoraufruf für den Wert verwendet wird, den er erzeugt, und nicht für den Nebeneffekt der Konstruktion. Meiner Meinung nach ist dies eine kleine Fehlfunktion." Ich würde mir vorstellen, dass ein Faktor bei dieser Entscheidung eine Rolle spielt war , dass , wenn der Compiler es verboten, es keine saubere fix in Fällen wäre , wo Sie sind es für einen Nebeneffekt aufrufen. (Die meisten anderen Anweisungen für verbotene Ausdrücke haben fremde Teile, die keinen Zweck erfüllen und entfernt werden können, mit Ausnahme von ... ? ... : ..., wo der Fix if/ elsestattdessen verwendet werden soll.)
Ruakh
1
@ruakh: Da dies ein Verhalten ist, von dem abgeraten werden sollte, ist eine saubere Korrektur nicht erforderlich, solange es eine einigermaßen billige / einfache Lösung gibt. Was gibt es; Weisen Sie es einer Variablen zu und verwenden Sie diese Variable nicht. Wenn Sie deutlich machen möchten, dass Sie es nicht verwenden, geben Sie dieser Codezeile ihren eigenen Gültigkeitsbereich. Während man technisch argumentieren könnte, dass dies die Semantik des Codes ändert (eine nutzlose Referenzzuweisung ist immer noch eine nutzlose Referenzzuweisung), ist es in der Praxis unwahrscheinlich, dass dies eine Rolle spielt. Ich gebe zu, dass es etwas andere IL erzeugt ... aber nur, wenn es ohne Optimierungen kompiliert wird.
Brian
Safari, Firefox und Chrome geben beim Eingeben ReferenceError an contineu;.
Brian McCutchon
2
@ McBrainy: Du hast recht. Ich erinnere mich falsch an den Fehler, der sich aus der Rechtschreibfehler ergibt. Ich werde meine Notizen überprüfen. In der Zwischenzeit habe ich die beleidigende Aussage gelöscht, da sie für den größeren Punkt hier nicht von Belang ist.
Eric Lippert
1
@ Mike: Ich stimme zu. Eine andere Situation, in der wir die Aussage manchmal sehen, sind Testfälle wie, try { new Foo(null); } catch (ArgumentNullException)...aber offensichtlich sind diese Situationen per Definition kein Produktionscode. Es erscheint vernünftig, einen solchen Code so zu schreiben, dass er einer Dummy-Variablen zugewiesen wird.
Eric Lippert
46

In der C # -Sprachspezifikation

Ausdrucksanweisungen werden verwendet, um Ausdrücke auszuwerten. Ausdrücke, die als Anweisungen verwendet werden können, umfassen Methodenaufrufe, Objektzuweisungen mit dem neuen Operator, Zuweisungen mit = und den zusammengesetzten Zuweisungsoperatoren, Inkrementierungs- und Dekrementierungsoperationen mit den Operatoren ++ und - und warten auf Ausdrücke.

Wenn Sie eine Anweisung in Klammern setzen, wird ein neuer sogenannter Ausdruck in Klammern erstellt. Aus der Spezifikation:

Ein Ausdruck in Klammern besteht aus einem Ausdruck in Klammern. ... Ein Ausdruck in Klammern wird ausgewertet, indem der Ausdruck in Klammern ausgewertet wird. Wenn der Ausdruck in Klammern einen Namespace oder Typ angibt, tritt ein Fehler bei der Kompilierung auf. Andernfalls ist das Ergebnis des Ausdrucks in Klammern das Ergebnis der Bewertung des enthaltenen Ausdrucks.

Da Ausdrücke in Klammern nicht als gültige Ausdrucksanweisung aufgeführt sind, handelt es sich nicht um eine gültige Anweisung gemäß der Spezifikation. Warum sich die Designer für diese Vorgehensweise entschieden haben, ist unklar, aber ich wette, dass Klammern keine nützliche Arbeit leisten, wenn die gesamte Aussage in Klammern steht: stmtund (stmt)genau gleich sind.

Frank Bryce
quelle
4
@Servy: Wenn Sie genau lesen, war es etwas nuancierter. Die sogenannte "Ausdrucksanweisung" ist in der Tat eine gültige Anweisung, und die Spezifikation listet die Arten von Ausdrücken auf, die gültige Anweisungen sein können. Ich wiederhole jedoch aus der Spezifikation, dass der Ausdruckstyp "Ausdruck in Klammern" NICHT in der Liste der gültigen Ausdrücke enthalten ist, die als gültige Ausdrucksanweisungen verwendet werden können.
Frank Bryce
4
Das OPfragt nichts nach dem Sprachdesign. Er will nur wissen, warum das ein Fehler ist. Die Antwort lautet: weil es keine gültige Aussage ist .
Leandro
9
OK, mein schlechtes. Die Antwort lautet: Weil es sich laut Spezifikation nicht um eine gültige Aussage handelt. Da haben Sie es: einen Designgrund !
Leandro
3
Die Frage ist nicht nach einem tiefen, designorientierten Grund gefragt, warum die Sprache so konzipiert wurde, dass sie mit dieser Syntax umgeht. Er fragt, warum ein Code kompiliert wird, wenn ein anderer Code (der für Anfänger so aussieht, als sollte er sich verhalten) identisch) kann nicht kompiliert werden. Dieser Beitrag beantwortet das.
Magier Xy
4
@Servy JohnCarpenter sagt nicht nur "Das kannst du nicht." Er sagt, diese beiden Dinge, durch die Sie verwirrt waren, sind nicht die gleichen. j ++ ist eine Ausdrucksanweisung, während (j ++) ein Ausdruck in Klammern ist. Plötzlich kennt das OP den Unterschied und was sie sind. Das ist eine gute Antwort. Es beantwortet den Hauptteil seiner Frage, nicht den Titel. Ich denke, dass das Wort "Design" im Titel der Frage viel Streit hervorruft, aber dies muss nicht von den Designern beantwortet werden, sondern nur aus den Spezifikationen entnommen werden.
thinklarge
19

Weil die Klammern um i++einen Ausdruck erstellen / definieren .. wie in der Fehlermeldung angegeben .. kann ein einfacher Ausdruck nicht als Anweisung verwendet werden.

Warum wurde die Sprache so entworfen? um Fehler zu vermeiden, die irreführende Ausdrücke als Anweisungen haben und keine Nebenwirkungen wie den Code haben

int j = 5;
j+1; 

Die zweite Zeile hat keine Auswirkung (aber Sie haben es möglicherweise nicht bemerkt). Aber anstatt dass der Compiler ihn entfernt (weil der Code nicht benötigt wird), fordert er Sie ausdrücklich auf, ihn zu entfernen (damit Sie den Fehler bemerken) ODER ihn zu beheben, falls Sie vergessen haben, etwas einzugeben.

bearbeiten :

Um den Teil über Klammern klarer zu machen, werden Klammern in c # (neben anderen Verwendungszwecken wie Cast und Funktionsaufruf) verwendet, um Ausdrücke zu gruppieren und einen einzelnen Ausdruck zurückzugeben (Make der Unterausdrücke).

Auf dieser Codeebene sind nur Stamente erlaubt

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

Aber wenn Sie die Klammer verwenden, verwandeln Sie sie in einen Ausdruck

myTempExpression = (j++)

und das

myTempExpression;

ist nicht gültig, da der Compiler nicht sicherstellen kann, dass der Ausdruck als Nebeneffekt auftritt (nicht ohne dass das Problem beim Anhalten auftritt).

CaldasGSM
quelle
Ja, das habe ich mir auch zuerst gedacht. Dies hat jedoch einen Nebeneffekt. Es führt eine Nachinkrementierung der jVariablen durch, oder?
user10607
1
j++;ist eine gültige Aussage, aber unter Verwendung der Klammern, die Sie sagen let me take this stement and turn it into an expression.. und Ausdrücke sind zu diesem Zeitpunkt im Code nicht gültig
CaldasGSM
Es ist schade, dass es keine Form der Anweisung "Berechnen und Ignorieren" gibt, da der Code gelegentlich behaupten möchte, dass ein Wert ohne Auslösen einer Ausnahme berechnet werden kann, sich jedoch nicht um den so berechneten tatsächlichen Wert kümmert. Eine Compute-and-Ignor-Anweisung würde nicht besonders oft verwendet, würde aber in solchen Fällen die Absicht des Programmierers deutlich machen.
Supercat