Почему это недействительный синтаксис для вызова члена Action таким образом?

Следующий код создает синтаксическую ошибку:

class Foo
{
    public Action a = () => { };
}

void doSomething() 
{
    var foo = new Foo();
    (foo.a)(); // error CS1525: Invalid expression term ')'
}

Однако следующие альтернативы работают:

foo.a();                // works
Action a = foo.a; a();  // works

Почему это так? (foo.a) является Action; почему я не могу назвать это?

Ответы

Ответ 1

Что происходит?

Это потому, что фрагмент кода (foo.a)(); будет оценивать выражение-литье, поскольку foo.a может быть либо доступом к члену, либо типом. И поэтому компилятор ищет выражение внутри пустых скобок.

Например, рассмотрим следующий фрагмент:

Func<string, int> fn = null;
int x = (fn)("asd"); // The type or namespace "fn" could not be found

Здесь компилятор четко заявляет, что он интерпретировал его как выражение-cast, поскольку он ожидает, что fn будет типом.

Итак, внутри первого набора скобок ничего не может быть, что можно было бы читать как тип, потому что это заставит целое выражение читать как выражение-литье.

Следующий фрагмент компиляции:

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

Но эти строки не выполняются:

(Console.WriteLine)("asd");
Action a = null;
(a)();
Action<string> b = null;
(b)("");

Почему это происходит?

Оказывается, синтаксическая грамматика неоднозначна. Почему синтаксический анализатор интерпретирует (a)() как выражение-литье, когда это не так, но он может интерпретировать его как выражение-обращение? Когда парсер решает, будет ли он актом или вызовом? Ну, они подумали об этом. В спецификации указано (§7.7.6):

Грамматика для выражения-выражения приводит к определенным синтаксическим неоднозначностям. Например, выражение (x) -y может быть либо интерпретирован как выражение-литье (приведение типа -y к типу x), либо как комбинированное выражение с выражением в скобках (которое вычисляет значение x - y).

Чтобы устранить двусмысленности выражения, существует следующее правило: последовательность из одного или нескольких токенов (§ 2.3.3) заключенный в круглые скобки, считается началом выражения-выражения только в том случае, если верно хотя бы одно из следующих значений:

  • Последовательность токенов - это правильная грамматика для типа, но не для выражения.
  • Последовательность токенов - правильная грамматика для типа, а токен сразу после закрытия круглые скобки - это токен "~", токен "!", токен "(", идентификатор (п. 2.4.1), буквальный (§ 2.4.4) или любой ключевое слово (§2.4.3), кроме как и есть.

В этом случае "последовательность токенов - правильная грамматика для типа, а токен, следующий за закрывающимися скобками, - это токен (", поэтому это конец истории.

Синтаксическая грамматика

Из С# 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

Ответ 2

Выполняя (foo.a)(), синтаксически вы вынимаете () из области видимости/контекста (foo.a)

Значение, вы вызываете foo.a, который является синтаксически недействительным, а затем вызывает () в результате этой операции.

Другими словами, запрос () результата выражения (foo.a) вместо запроса a() foo.

Изменить: См. комментарии