Ответ 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