Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it invalid syntax to call an Action member in this way?

Tags:

c#

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?

like image 528
jcai Avatar asked Aug 28 '15 15:08

jcai


2 Answers

What does happen?

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)("");

Why does it happen?

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.

Syntactic grammar

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
like image 54
Tamas Hegedus Avatar answered Sep 29 '22 09:09

Tamas Hegedus


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

like image 22
Kaelan Fouwels Avatar answered Sep 29 '22 07:09

Kaelan Fouwels