Почему скобки конструктора инициализатора объекта C# 3.0 необязательны?

кажется, что синтаксис инициализатора объекта C# 3.0 позволяет исключить пару скобок open/close в конструкторе, когда существует конструктор без параметров. Пример:

var x = new XTypeName { PropA = value, PropB = value };

против:

var x = new XTypeName() { PropA = value, PropB = value };

мне любопытно, почему пара открытых/закрытых скобок конструктора необязательна здесь после XTypeName?

5 ответов


этот вопрос был темой моего блога 20 сентября 2010 года. Ответы Джоша и Чада ("они не добавляют никакой ценности, так зачем их требовать?"и" устранить избыточность") в основном верны. Чтобы конкретизировать это немного больше:

функция, позволяющая вам удалить список аргументов как часть "большей функции" инициализаторов объектов, встретила наш бар для" сладких " функций. Некоторые моменты мы рассмотрели:

  • стоимость проектирования и спецификации было низко
  • мы собирались широко изменить код парсера, который обрабатывает создание объекта в любом случае; дополнительные затраты на разработку необязательного списка параметров были невелики по сравнению со стоимостью большей функции
  • нагрузка тестирования была относительно небольшой по сравнению со стоимостью большей функции
  • бремя документации было относительно небольшим по сравнению...
  • ожидалось, что бремя обслуживания будет небольшим; I не помню никаких ошибок, сообщенных в этой функции за годы с момента ее отправки.
  • функция не представляет каких-либо сразу очевидных рисков для будущих функций в этой области. (Последнее, что мы хотим сделать, это сделать дешевую, легкую функцию сейчас, что делает гораздо труднее реализовать более привлекательную функцию в будущем.)
  • функция не добавляет новых двусмысленностей в лексический, грамматический или семантический анализ языка. Это не создает никаких проблем для " частичного программа " анализ, выполняемый движком IntelliSense IDE во время ввода текста. И так далее.
  • функция попадает в общее "сладкое пятно" для большей функции инициализации объекта; обычно, если вы используете инициализатор объекта, это именно потому, что конструктор объекта делает не позволяет установить свойства, которые вы хотите. Очень часто такие объекты просто являются "мешками свойств", которые не имеют параметров в 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 будет неоднозначной. мы должны были бы тогда придумать правило, разрешающее двусмысленность; мы не могли бы сделать это потому что тогда это было бы разрывное изменение это изменяет существующую юридическую программу C# на сломанную программу.

поэтому правило должно быть очень сложным: по существу, круглые скобки являются необязательными только в тех случаях, когда они не вводят двусмысленности. Мы должны были бы проанализировать все возможные случаи, которые вводят двусмысленности, а затем написать код в компиляторе, чтобы обнаружить их.

в этом свете вернитесь и посмотрите на все расходы, о которых я упоминаю. Сколько из них теперь стали большими? Сложные правила имейте большие цены дизайна, спецификаций, развития, испытания и документации. Сложные правила гораздо чаще вызывают проблемы с неожиданными взаимодействиями с функциями в будущем.

все ради чего? Крошечная выгода для клиентов, которая не добавляет новой репрезентативной силы языку, но добавляет сумасшедшие угловые случаи, просто ожидающие, чтобы кричать "gotcha" на какую-то бедную ничего не подозревающую душу, которая сталкивается с ним. Характеристики как вырезать тут и поставить на "никогда не этот список.

как вы определили, что особые двусмысленности?

это было сразу ясно; я довольно хорошо знаком с правилами в C# для определения, когда ожидается пунктирное имя.

при рассмотрении новой функции как вы определяете, вызывает ли она какую-либо двусмысленность? Вручную, формальным доказательством, машинным анализом, что?

все три. В основном мы просто смотрим на спецификации и лапшу это, как я сделал выше. Например, предположим, что мы хотим добавить новый префиксный оператор в C# под названием "frob":

x = frob 123 + 456;

(обновление: frob конечно await; анализ здесь по существу анализ, который команда разработчиков прошла при добавлении await.)

"frob" здесь похоже на" new " или " ++ " - он приходит перед выражением какого-то рода. Мы бы разработали желаемый приоритет и ассоциативность и так далее, а затем начать задавать вопросы, как "что, если программа уже есть тип, поле, свойство, событие, метод, константа или локальное имя frob?"Это сразу же приведет к таким случаям, как:

frob x = 10;

означает ли это " выполнить операцию frob в результате x = 10 или создать переменную типа frob с именем x и назначить ей 10?"(Или, если frobbing производит переменную, это может быть присвоение 10 к frob x. Ведь *x = 10; анализирует и является законным, если x is int*.)

G(frob + x)

означает ли это " frob the результат унарного оператора plus на x"или" добавить выражение frob в x"?

и так далее. Чтобы разрешить эти двусмысленности, мы могли бы ввести эвристику. Когда вы говорите "var x = 10", это неоднозначно; это может означать" вывод типа x "или"x имеет тип var". Итак, у нас есть эвристика: сначала мы пытаемся найти тип с именем var, и только если он не существует, мы делаем вывод о типе X.

или мы можем изменить синтаксис, чтобы он не был двусмысленным. Когда они разработанный C# 2.0 у них была эта проблема:

yield(x);

означает ли это "выход x в итераторе" или "вызов метода yield с аргументом x?- Изменив его на

yield return(x);

теперь это однозначно.

в случае необязательных паренов в инициализаторе объекта легко рассуждать о том, есть ли двусмысленности, введенные или нет, потому что количество ситуаций, в которых допустимо вводить что-то, что начинается с { is очень маленький. В основном просто различные контексты операторов, операторы lambdas, инициализаторы массива и все. Легко рассуждать во всех случаях и показать, что нет никакой двусмысленности. Убедитесь, что IDE остается эффективным несколько сложнее, но может быть сделано без особых проблем.

такого рода возни со спецификацией обычно достаточно. Если это особенно сложная функция, то мы вытаскиваем более тяжелые инструменты. Например, при проектировании LINQ, один из ребята компилятора и один из парней IDE, которые оба имеют опыт в теории парсеров, построили себе генератор парсеров, который мог анализировать грамматики, ищущие двусмысленности, а затем кормил предлагаемые грамматики C# для понимания запросов в него; это обнаружило много случаев, когда запросы были неоднозначными.

или, когда мы сделали расширенный вывод типа на lambdas в C# 3.0, мы написали наши предложения, а затем отправили их через пруд в Microsoft Research в Кембридже, где языковая команда было достаточно хорошо, чтобы разработать формальное доказательство того, что предложение типа вывода было теоретически обоснованным.

есть ли двусмысленности в C# сегодня?

конечно.

G(F<A, B>(0))

в C# 1 ясно, что это значит. Это то же самое, что:

G( (F<A), (B>0) )

то есть он вызывает G с двумя аргументами, которые являются булами. В C# 2 это может означать то, что это означало В C# 1, но это также может означать " передать 0 в общий метод F, который принимает тип параметры A и B, а затем передают результат F в G". Мы добавили сложную эвристику к синтаксическому анализатору, который определяет, какой из двух случаев вы, вероятно, имели в виду.

аналогично, слепки неоднозначны даже в C# 1.0:

G((T)-x)

это " cast-x to T "или"вычесть x из T"? Опять же, у нас есть эвристика, которая делает хорошее предположение.


потому что именно так был указан язык. Они не добавляют ценности, так зачем их включать?

это также очень похоже на implicity типизированные массивы

var a = new[] { 1, 10, 100, 1000 };            // int[]
var b = new[] { 1, 1.5, 2, 2.5 };            // double[]
var c = new[] { "hello", null, "world" };      // string[]
var d = new[] { 1, "one", 2, "two" };         // Error

ссылка:http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx


Это было сделано для упрощения строительства объектов. Языковые дизайнеры (насколько мне известно) конкретно не сказали, почему они чувствовали, что это полезно, хотя это явно упоминается в страница спецификации C# версии 3.0:

выражение создания объекта может опустить список аргументов конструктора и заключить скобки, если оно включает инициализатор объекта или коллекции. Опустить список аргументов конструктора и заключить скобки эквивалентны указанию пустого списка аргументов.

Я полагаю, что они чувствовали, что скобки в этом случае не были необходимы, чтобы показать намерение разработчика, поскольку инициализатор объекта показывает намерение построить и установить свойства объекта.


в первом примере компилятор делает вывод, что вы вызываете конструктор по умолчанию (спецификация языка C# 3.0 гласит, что если скобки не указаны, вызывается конструктор по умолчанию).

во втором случае вы явно вызываете конструктор по умолчанию.

вы также можете использовать этот синтаксис для задания свойств при явной передаче значений конструктору. Если у вас было следующее определение класса:

public class SomeTest
{
    public string Value { get; private set; }
    public string AnotherValue { get; set; }
    public string YetAnotherValue { get; set;}

    public SomeTest() { }

    public SomeTest(string value)
    {
        Value = value;
    }
}

все три утверждения действительны:

var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" };
var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"};
var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"};

Я не Эрик Липперт, поэтому я не могу сказать наверняка, но я бы предположил, что это потому, что пустая скобка не нужна компилятору для вывода конструкции инициализации. Поэтому информация становится избыточной, а не необходимой.