Why was the statement (j++); forbidden?
Asked Answered
O

3

170

The following code is wrong (see it on ideone):

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

error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

  1. Why does the code compile when we remove the parentheses?
  2. Why does it not compile with the parentheses?
  3. Why was C# designed that way?
Octopus answered 22/12, 2015 at 18:24 Comment(12)
@Servy lots of people involved with C# language design are on SO, so thats why I asked. Anything wrong with this?Octopus
The compiler expects a statement here, but these brackets indicate a value.Jumna
I can't imagine a more on-topic question than "why does a specific programming language handle this specific behavior in this way?"Civet
@Servy feels like it would fall under a constructive subjective question, stackoverflow.com/help/dont-askFunke
@Funke Considering the answer has already attracted 4 answers that don't follow those points, clearly it's not. It's also not a specifically practical program problem.Osswald
@Osswald - from the question, someone could reasonably pontificate on the purpose behind why it's not valid ("Deep insights appreciated"). There are some very knowledgable C# experts who might understand the ramifications of the decision despite not being the creators of the language. The fact that the answers have yet to do so, is perhaps a reflection of the clarity of the question, but not the intent - for what it's worth, that was my reading, but I do not have the expertise to answer.Funke
Now we have an answer of Eric Lippert. Maybe you want to re-think your decision about the accepted answer. It's Christmas, so perhaps you have accepted the answer too early.Reforest
@Osswald Are you implying that design decisions are never documented and are absolutely unknown to everyone else? He's not asking SO users to guess, he's asking for an answer - that does not imply he's asking for a guess. Whether people answer with a guess is on them, not him. And as OP pointed out, there are people on stack overflow who did actually work on C# and made these decisions.Apodosis
@Rob: He's not implying that design decisions are never documented. He's also not implying that the question isn't interesting. He's saying that the question probably needs an answer from the authors themselves, and thus is not suitable for SO, as it leads to wild speculation, as demonstrated here. Eric Lippert now gets as authoritative as anyone could ask for, which saves this question.Denna
@Denna Well, I disagree; I don't believe an answer must come from the authors themselves, though of course their answer would be the best possible outcome. Many questions can lead to wild speculation, but that's a problem of the answerers, not the asker. There are plenty of people who post guesses as answers; which makes the answer bad, not the question.Apodosis
@Rob: The problem is that, yes, unless the rationale is already documented somewhere, it must, but speculation is fun and who doesn't want to share his own? If you find a way to make sure such pure (or "educated") guesses are reliably culled, tell me and we'll make a fortune.Denna
I'm more interested to know if you can do that in any language and why you'd want to? It's hideous and makes very little sense.Willamina
M
222

Deep insights appreciated.

I shall do my best.

As other answers have noted, what's going on here is the compiler is detecting that an expression is being used as a statement. In many languages -- C, JavaScript, and many others -- it is perfectly legal to use an expression as a statement. 2 + 2; is legal in these languages, even though this is a statement that has no effect. Some expressions are useful only for their values, some expressions are useful only for their side effects (such as a call to a void returning method) and some expressions, unfortunately, are useful for both. (Like increment.)

Point being: statements that consist only of expressions are almost certainly errors unless those expressions are typically thought of as more useful for their side effects than their values. C# designers wished to find a middle ground, by allowing expressions that were generally thought of as side-effecting, while disallowing those that are also typically thought of as useful for their values. The set of expressions they identified in C# 1.0 were increments, decrements, method calls, assignments, and somewhat controversially, constructor invocations.


ASIDE: One normally thinks of an object construction as being used for the value it produces, not for the side effect of the construction; in my opinion allowing new Foo(); is a bit of a misfeature. In particular, I've seen this pattern in real-world code that caused a security defect:

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

It can be surprisingly hard to spot this defect if the code is complicated.


The compiler therefore works to detect all statements that consist of expressions that are not on that list. In particular, parenthesized expressions are identified as just that -- parenthesized expressions. They are not on the list of "allowed as statement expressions", so they are disallowed.

All of this is in service of a design principle of the C# language. If you typed (x++); you were probably doing something wrong. This is probably a typo for M(x++); or some just thing. Remember, the attitude of the C# compiler team is not "can we figure out some way to make this work?" The attitude of the C# compiler team is "if plausible code looks like a likely mistake, let's inform the developer". C# developers like that attitude.

Now, all that said, there actually are a few odd cases where the C# specification does imply or state outright that parentheses are disallowed but the C# compiler allows them anyways. In almost all those cases the minor discrepancy between the specified behaviour and the allowed behaviour is completely harmless, so the compiler writers have never fixed these small bugs. You can read about those here:

Is there a difference between return myVar vs. return (myVar)?

Mention answered 22/12, 2015 at 20:56 Comment(7)
Re: "One normally thinks of a constructor invocation as being used for the value it produces, not for the side effect of the construction; in my opinion this is a bit of a misfeature": I'd imagine that one factor in this decision was that if the compiler forbade it, there wouldn't be any clean fix in cases where you are calling it for a side-effect. (Most other forbidden expression statements have extraneous parts that serve no purpose and can be removed, with the exception of ... ? ... : ..., where the fix is to use if/else instead.)Jorgenson
@ruakh: Since this is behavior that should be discouraged, a clean fix is not necessary, so long as there is a reasonably cheap/simple fix. Which there is; assign it to a variable and don't use that variable. If you want to be blatant that you aren't using it, give that line of code it's own scope. While technically one could argue that this changes the code's semantics (a useless reference assignment is still a useless reference assignment), in practice it is unlikely to matter. I admit that it does generate slightly different IL... but only if compiled without optimizations.Cambric
Safari, Firefox, and Chrome all give ReferenceError when typing contineu;.Girovard
@McBrainy: You're right. I am misremembering the defect that results from the misspelling; I'll check my notes. In the meanwhile I've deleted the offending statement as it is not germane to the larger point here.Mention
As an aside, while many other languages might not make it an error to use an expression without side-effects as a statement, most compilers for them, when using reasonable options, will still warn about it.Denna
Regarding new Foo() as a statement, it's a bad design pattern in general, although it's possible to see a small number of situations in which it could be useful. Eg, imagine if there was a way to create a new Thread (or custom threaded object) that can automatically start, and thus no need to do new Thread(...).Start();, which satisfies the need to call something. I can't immediately think of an example, but I wouldn't be surprised if there's something that has useful output in the constructor (of course, it'd have to be capable of more to justify creating a new object normally).Abode
@Mike: I agree. Another situation in which we sometimes see the statement is test cases like try { new Foo(null); } catch (ArgumentNullException)... but obviously these situations are by definition not production code. It seems reasonable that such code could be written to assign to a dummy variable.Mention
D
46

In the C# language specification

Expression statements are used to evaluate expressions. Expressions that can be used as statements include method invocations, object allocations using the new operator, assignments using = and the compound assignment operators, increment and decrement operations using the ++ and -- operators and await expressions.

Putting parentheses around a statement creates a new so-called parenthesized expression. From the specification:

A parenthesized-expression consists of an expression enclosed in parentheses. ... A parenthesized-expression is evaluated by evaluating the expression within the parentheses. If the expression within the parentheses denotes a namespace or type, a compile-time error occurs. Otherwise, the result of the parenthesized-expression is the result of the evaluation of the contained expression.

Since parenthesized expressions are not listed as a valid expression statement, it is not a valid statement according to the specification. Why the designers chose to do it this way is anyone's guess but my bet is because parentheses do no useful work if the entire statement is contained in parentheses: stmt and (stmt) are exactly the same.

Deuteron answered 22/12, 2015 at 18:30 Comment(7)
The question has already quoted the error message specifically stating that expressions cannot be statements. The question is asking why the language was designed that way; you have done nothing to answer that question, you've merely stated that expressions aren't valid statements in C#, which was a premise of the question.Osswald
@Servy: If you read closely, it was slightly more nuanced than that. The so-called "Expression Statement" is indeed a valid statement, and the specification lists the types of expressions that can be valid statements. However, I repeat from the spec that the type of expression called the "parenthesized expression" is NOT on the list of valid expressions which can be used as valid expression statements.Deuteron
The OP is asking nothing about the language design. He just wants to know why this is an error. The answer is: because it is not a valid statement.Giovannigip
OK, my bad. The answer is: because, according to the specification, it is not a valid statement. There you have it: a design reason!Giovannigip
The question is not asking for a deep, design-driven reason as to why the language was designed to handle this syntax in this way - he's asking why one piece of code compiles when another piece of code (which to beginners looks like it should behave identically) fails to compile. This post answers that.Civet
@Osswald JohnCarpenter isn't just saying "You can't do that." He's saying, those two things you were confused by aren't the same. j++ is an Expression Statement, while (j++) is a parenthesized-expression. All of a sudden the OP now knows the difference and what they are. That is a good answer. It answers the body of his question not the title. I think a lot of contention comes from the word "design" in the title of the question, but this doesn't have to be answered by the designers, just gleaned from the specifications.Caesura
@Servy: "Why is this simple code giving error" "Why does it not compile with the brackets?" I'm not sure how else to interpret those questions. It seems extremely straightforward to me. Perhaps you are taking issue with the word "design" coming right after C# in the question title. If that word was taken out, all you'd have left is the exact question that's been answered here.Civet
O
19

beacause the brackets around the i++ are creating/defining an expression.. as the error message says.. a simple expression cant be used as a statement.

why the language was designed to be this way? to prevent bugs, having misleading expressions as statements, that produce no side effect like having the code

int j = 5;
j+1; 

the second line has no effect(but you may not have noticed). But instead of the compiler removing it(because the code is not needed).it explicitly asks you to remove it(so you will be aware or the error) OR fix it in case you forgot to type something.

edit:

to make the part about bracked more clear.. brackets in c# (besides other uses, like cast and function call), are used to group expressions, and return a single expression (make of the sub expressions).

at that level of code only staments are allowed.. so

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

but by using the bracked you are turning it into as expression

myTempExpression = (j++)

and this

myTempExpression;

is not valid because the compiler cant asure that the expression as side effect.(not without incurring into the halting problem)..

Obedient answered 22/12, 2015 at 18:34 Comment(3)
Yeah, that's what I thought at first too. But this does have a side effect. It performs post–increment of j variable right?Octopus
j++; is a valid statement, but by using the brackets you are saying let me take this stement and turn it into an expression.. and expresions are not a valid at that point in codeObedient
It's too bad there's no form of "compute and ignore" statement, since there are occasions when code may wish to assert that a value can be computed without throwing an exception, but not care about the actual value thus computed. A compute-and-ignore statement wouldn't be used terribly often, but would make the programmer's intention clear in such cases.Proud

© 2022 - 2024 — McMap. All rights reserved.