Ответ 1
Этот вопрос был предметом моего блога 20 сентября 2010 года. Джош и Чад отвечают ( "они не добавляют никакой ценности, поэтому зачем их требовать?" И "устранить избыточность" ) в основном правильны. Чтобы добавить это немного больше:
Функция, позволяющая вам элиминировать список аргументов как часть "большей функции" инициализаторов объектов, встретила наш бар для "сахаристых" функций. Некоторые моменты, которые мы рассмотрели:
- стоимость разработки и спецификации была низкой.
- мы собирались интенсивно изменять код парсера, который все равно обрабатывает создание объекта; дополнительные затраты на разработку, чтобы сделать список параметров необязательным, были невелики по сравнению со стоимостью более крупной функции.
- бремя тестирования было относительно небольшим по сравнению со стоимостью более крупной функции
- объем документации был относительно небольшим по сравнению с...
- ожидается, что бремя обслуживания будет небольшим; Я не помню никаких ошибок, о которых сообщалось в этой функции за годы, прошедшие с момента ее отправки.
- данная функция не представляет никаких очевидных рисков для будущих функций в этой области. (Последнее, что мы хотим сделать, это сделать дешевую и удобную функцию, которая значительно усложняет реализацию более привлекательной функции в будущем.)
- функция не добавляет новых двусмысленностей в лексический, грамматический или семантический анализ языка. Он не представляет проблем для такого рода "частичной программы", который выполняется при работе IDE "IntelliSense" во время ввода. И так далее.
- функция попадает в общее "сладкое пятно" для большей функции инициализации объекта; обычно, если вы используете инициализатор объекта, именно потому, что конструктор объекта не позволяет вам устанавливать нужные свойства. Для таких объектов очень часто просто быть "имущественными мешками", которые вначале не имеют параметров в ctor.
Почему же вы также не сделали пустые круглые скобки необязательными в вызове конструктора по умолчанию выражения создания объекта, у которого нет инициализатора объекта?
Взгляните на этот список критериев выше. Один из них заключается в том, что это изменение не вносит никакой новой двусмысленности в лексическом, грамматическом или семантическом анализе программы. Предложенное вами изменение вносит неопределенность в семантический анализ:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
Строка 1 создает новый C, вызывает конструктор по умолчанию, а затем вызывает метод экземпляра M на новом объекте. Строка 2 создает новый экземпляр B.M и вызывает его конструктор по умолчанию. Если скобки в строке 1 были необязательными, строка 2 была бы двусмысленной. Мы должны были бы придумать правило, разрешающее двусмысленность; мы не могли сделать это ошибкой, потому что это было бы изменением, которое изменило бы существующую законную программу С# в сломанную программу.
Поэтому правило должно быть очень сложным: по существу, скобки являются необязательными только в тех случаях, когда они не вводят неоднозначностей. Мы должны проанализировать все возможные случаи, которые вводят неоднозначность, а затем написать код в компиляторе для их обнаружения.
В этом свете вернитесь и посмотрите на все издержки, о которых я упоминаю. Сколько из них теперь стали большими? Сложные правила имеют большие затраты на проектирование, спецификации, разработку, тестирование и документацию. Сложные правила гораздо чаще вызывают проблемы с неожиданными взаимодействиями с функциями в будущем.
Все для чего? Крошечная польза для клиентов, которая не добавляет новых репрезентативных возможностей для языка, но добавляет сумасшедшие угловые случаи, ожидая, чтобы кричать "gotcha" у какой-то плохой ничего не подозревающей души, которая сталкивается с этим. Такие функции немедленно разрезаются и накладываются на список "никогда не делай этого".
Как вы определили эту конкретную двусмысленность?
Это было немедленно ясно; Я хорошо знаком с правилами в С# для определения, когда ожидается точечное имя.
При рассмотрении новой функции, как вы определяете, вызывает ли она какую-либо двусмысленность? Рукой, формальным доказательством, машинным анализом, что?
Все три. В основном мы просто смотрим на спекулянт и лапшу на нем, как я это делал выше. Например, предположим, что мы хотели добавить новый префиксный оператор в С#, называемый "frob":
x = frob 123 + 456;
(UPDATE: frob
, конечно, await
, анализ здесь - это, по существу, анализ, который команда дизайнеров прошла при добавлении await
.)
"frob" здесь похож на "новый" или "++" - он приходит перед выражением какого-то рода. Мы разработали желаемый приоритет и ассоциативность и так далее, а затем начнем задавать вопросы типа "что, если у программы уже есть тип, поле, свойство, событие, метод, константа или локальный, называемый frob?" Это немедленно приведет к таким случаям, как:
frob x = 10;
означает ли это "выполнить операцию frob по результату x = 10 или создать переменную типа frob, называемую x, и назначить ей 10?" (Или, если frobbing создает переменную, это может быть назначение от 10 до frob x
. В конце концов, *x = 10;
анализирует и является законным, если x
- int*
.)
G(frob + x)
Означает ли это, что "frob результат унарного оператора plus на x" или "добавить выражение frob to x"?
И так далее. Чтобы устранить эти двусмысленности, мы можем ввести эвристику. Когда вы говорите "var x = 10;" это неоднозначно; это может означать "вывести тип x", или это может означать "x имеет тип var". Итак, у нас есть эвристика: сначала мы пытаемся найти тип с именем var, и только если этого не существует, мы делаем вывод о типе x.
Или мы можем изменить синтаксис, чтобы он не был двусмысленным. Когда они спроектировали С# 2.0, у них возникла такая проблема:
yield(x);
Означает ли это "выход x в итераторе" или "вызов метода yield с аргументом x?" Изменив его на
yield return(x);
теперь недвусмысленно.
В случае необязательных парсеров в инициализаторе объекта нетрудно рассуждать о том, введены ли двусмысленности или нет, потому что число ситуаций, в которых разрешено вводить что-то, начинающееся с {очень мало. В основном просто различные контексты операторов, инструкции lambdas, инициализаторы массивов и об этом. Легко рассуждать во всех случаях и показать, что нет никакой двусмысленности. Убедиться, что среда IDE эффективна, она несколько сложнее, но может быть выполнена без особых проблем.
Обычно такого рода возиться со спецификацией достаточно. Если это особенно сложная функция, мы вытаскиваем более тяжелые инструменты. Например, при разработке LINQ один из компиляторов и один из ребят из среды IDE, у которых есть фон в теории парсеров, построили генератор парсера, который мог анализировать грамматики, ищущие двусмысленности, а затем подавал предложенные С# грамматики для понимания запросов в него; при этом было найдено много случаев, когда запросы были неоднозначными.
Или, когда мы сделали расширенный вывод типа на lambdas в С# 3.0, мы написали наши предложения, а затем отправили их через пруд в Microsoft Research в Кембридже, где команда языков была достаточно хороша, чтобы разработать официальное доказательство того, что тип предложение вывода было теоретически обоснованным.
Есть ли двусмысленности в С# сегодня?
Конечно.
G(F<A, B>(0))
В С# 1 ясно, что это значит. Это то же самое, что:
G( (F<A), (B>0) )
То есть, он вызывает G с двумя аргументами, которые являются bools. В С# 2 это может означать, что это означает в С# 1, но это также может означать "передать 0 в общий метод F, который принимает параметры типа A и B, а затем передает результат F в G". Мы добавили сложную эвристику в парсер, который определяет, какой из двух случаев вы, вероятно, имели в виду.
Аналогично, отливки нечетны даже в С# 1.0:
G((T)-x)
Это "cast -x to T" или "вычесть x из T"? Опять же, у нас есть эвристика, которая делает хорошее предположение.