Подтвердить что ты не робот

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

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

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

В отличие от:

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

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

4b9b3361

Ответ 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"? Опять же, у нас есть эвристика, которая делает хорошее предположение.

Ответ 2

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

Он также очень похож на массивы с имплантированным типом

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

Ответ 3

Это было сделано для упрощения построения объектов. Разработчики языка, по-моему, специально не сказали, почему они считают, что это было полезно, хотя оно явно упоминается на странице С# Version 3.0:

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

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

Ответ 4

В вашем первом примере компилятор указывает, что вы вызываете конструктор по умолчанию (спецификация языка С# 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 = "!"};

Ответ 5

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