Почему скобки конструктора инициализатора объекта C # 3.0 являются необязательными? - PullRequest
109 голосов
/ 07 сентября 2010

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

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

В отличие от:

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

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

Ответы [ 5 ]

139 голосов
/ 07 сентября 2010

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

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

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

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

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

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

Как вы определили эту конкретную двусмысленность?

Это сразу стало ясно;Я довольно хорошо знаком с правилами в C # для определения ожидаемого точечного имени.

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

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

x = frob 123 + 456;

(UPDATE: frob - это, конечно, await; анализ здесь, по сути, заключается в анализе того, чтоКоманда разработчиков прошла через добавление await.)

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

frob x = 10;

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

G(frob + x)

Означает ли это «frob результат унарного оператора плюс на x» или «добавить выражение frob к x»?

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

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

yield(x);

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

yield return(x);

, теперь он становится однозначным.

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

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

Или, когда мы сделали расширенный вывод типов для лямбда-выражений в 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)

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

10 голосов
/ 07 сентября 2010

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

Это также очень похоже на массивы типа 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

7 голосов
/ 07 сентября 2010

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

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

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

4 голосов
/ 07 сентября 2010

В первом примере компилятор делает вывод, что вы вызываете конструктор по умолчанию (спецификация языка 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 = "!"};
1 голос
/ 07 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...