The following code produces a syntax error:
class Foo
{
public Action a = () => { };
}
void doSomething()
{
var foo = new Foo();
(foo.a)(); // error CS1525: Invalid expression term ')'
}
However, the following alternatives both work:
foo.a(); // works
Action a = foo.a; a(); // works
Why is this so? (foo.a)
is an Action
; why can't I call it?
That is because the piece of code (foo.a)();
would evaluate to a cast-expression, as foo.a
could be either a member-access or a type.
And that's why the compiler is searching for an expression inside the empty brackets.
For example consider the following snippet:
Func<string, int> fn = null;
int x = (fn)("asd"); // The type or namespace "fn" could not be found
Here the compiler clearly states that it interpreted it as a cast-expression, as it expects fn
to be a type.
So, inside the first set of brackets nothing can be that could be read as a type, because that will cause the whole expression to be read as a cast-expression.
The following snippet compiles:
(new Action<int>(x => { Console.WriteLine(x); }))(1);
(new Action(() => { Console.WriteLine("asd1"); }))();
((Console.WriteLine))("asd2");
((Action)null)();
Action a = null;
(true ? a : null)();
((a))();
But these lines do not:
(Console.WriteLine)("asd");
Action a = null;
(a)();
Action<string> b = null;
(b)("");
It turns out that the syntactic grammar is ambiguous. Why does the parser interpret (a)()
as a cast-expression, when it is not, but it could interpret it as an invocation-expression? When does the parser decide if it is going to be a cast or an invocation? Well, they thought of this. The specification says (§7.7.6):
The grammar for a cast-expression leads to certain syntactic ambiguities. For example, the expression (x)–y could either be interpreted as a cast-expression (a cast of –y to type x) or as an additive-expression combined with a parenthesized-expression (which computes the value x – y).
To resolve cast-expression ambiguities, the following rule exists: A sequence of one or more tokens (§2.3.3) enclosed in parentheses is considered the start of a cast-expression only if at least one of the following are true:
- The sequence of tokens is correct grammar for a type, but not for an expression.
- The sequence of tokens is correct grammar for a type, and the token immediately following the closing parentheses is the token “~”, the token “!”, the token “(”, an identifier (§2.4.1), a literal (§2.4.4), or any keyword (§2.4.3) except as and is.
In this case, "The sequence of tokens is correct grammar for a type, and the token immediately following the closing parentheses is the token (
" stands, so this is the end of the story.
From the C# 5.0 Language Specification
cast-expression:
( type ) unary-expression
unary-expression:
primary-expression
...
cast-expression
await-expression
expression:
non-assignment-expression
assignment
non-assignment-expression:
conditional-expression
lambda-expression
query-expression
// conditional expression at the end can contain a single unary-expression
invocation-expression:
primary-expression ( argument-list_opt )
primary-expression:
primary-no-array-creation-expression
array-creation-expression
primary-no-array-creation-expression:
literal
simple-name
parenthesized-expression
...
parenthesized-expression:
( expression )
expression-statement:
statement-expression ;
statement-expression:
invocation-expression
object-creation-expression
assignment
post-increment-expression
post-decrement-expression
pre-increment-expression
pre-decrement-expression
await-expression
By doing (foo.a)()
, syntactically you are taking the ()
out of the 'scope'/context of (foo.a)
Meaning, you are calling foo.a
, which is syntactically invalid, then calling ()
on the result of that operation.
In other words, asking for ()
on the result of the expression (foo.a)
, rather than requesting a()
of foo
.
Edit: See comments
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With