Чому в скобках конструктора об’єктів ініціалізатора об'єктів C # 3.0 необов’язкові?


114

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

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

На відміну від:

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

Мені цікаво, чому пара конструктора відкривати / закривати дужки тут необов'язково XTypeName?


9
Ми відзначили це в огляді коду минулого тижня: var list = новий Список <Foo> {}; Якщо чимось можна зловживати ...
blu

@blu Ось одна причина, яку я хотів задати це питання. Я помітив невідповідність у нашому коді. Невідповідність взагалі мене турбує, тому я подумав, що я буду бачити, чи є сильне обґрунтування необов’язковості в синтаксисі. :)
Джеймс Данн

Відповіді:


143

Це питання було предметом мого блогу 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 створює новий екземпляр BM та викликає його конструктор за замовчуванням. Якщо дужки в рядку 1 були необов'язковими, то рядок 2 був би неоднозначним. Тоді нам би довелося придумати правило, що вирішує неоднозначність; ми не змогли зробити це помилкою, тому що це було б перервою, яка змінює існуючу юридичну програму C # на зламану програму.

Тому правило повинно бути дуже складним: по суті, дужки є необов'язковими лише у випадках, коли вони не вносять двозначності. Нам доведеться проаналізувати всі можливі випадки, що вносять двозначності, а потім записати код у компілятор, щоб їх виявити.

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

Все для чого? Крихітна вигода для покупців, яка не додає мові нової представницької сили, але додає шалених кутових випадків, просто чекаючи, щоб кричати "готча" на якусь бідну, нічого не підозрюючу душу, яка натикається на неї. Такі функції негайно скорочуються та вносяться до списку "ніколи цього не роби".

Як ти визначив цю конкретну неоднозначність?

Це було відразу зрозуміло; Я досить добре знайомий з правилами в C # для визначення, коли очікується пунктирне ім'я.

Розглядаючи нову функцію, як визначити, чи викликає вона неоднозначність? Вручну, формальним доказом, машинним аналізом, що?

Усі три. В основному ми просто дивимося на специфікацію та локшину, як я це робив вище. Наприклад, припустимо, що ми хотіли додати новий оператор префікса до C # під назвою "frob":

x = frob 123 + 456;

(ОНОВЛЕННЯ: frobзвичайно await; аналіз тут - це по суті аналіз, який пройшла команда дизайнерів при додаванні await.)

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

frob x = 10;

чи означає це "зробити операцію frob за результатом x = 10, або створити змінну типу frob під назвою x та призначити 10 їй?" (Або, якщо фробінг створює змінну, це може бути призначення 10 - frob x. Зрештою, *x = 10;розбирає і є законним, якщо xє int*.)

G(frob + x)

Чи означає це "frob результат unry плюс оператора на x" або "add izraz frob to x"?

І так далі. Для вирішення цих неоднозначностей ми можемо ввести евристику. Коли ви говорите "var x = 10;" це неоднозначно; це може означати "висновок типу x" або може означати "x - тип var". Отже, у нас є евристичний: ми спершу намагаємося шукати тип з назвою var, і лише якщо такого не існує, ми робимо висновок про тип x.

Або ми можемо змінити синтаксис, щоб він був неоднозначним. Коли вони розробили C # 2.0, у них виникла така проблема:

yield(x);

Чи означає це "вихід x в ітераторі" або "виклик методу виходу аргументом 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 до Т" чи "відняти х від Т"? Знову ж таки, у нас є евристика, яка добре гадає.


3
Пробачте, я забув ... Підхід до сигналу кажана, хоча він, здається, працює, вважає за краще (ІМО) прямим засобом контакту, завдяки якому не можна отримати публічну витримку, яку потрібно шукати для публічної освіти у формі посади ЗО, яка є індексованим, пошуковим і може бути легко посилатися. Чи будемо ми замість цього безпосередньо контактувати, щоб хореографувати інсценований танець на пост / відповідь? :)
Джеймс Данн

5
Я рекомендую вам уникати постановки публікації. Це не було б справедливо до інших, хто може мати додаткове розуміння цього питання. Кращим підходом було б опублікувати питання, а потім надіслати посилання на нього з проханням про участь.
chilltemp

1
@James: я оновив свою відповідь, щоб вирішити ваше подальше запитання.
Ерік Ліпперт

8
@Eric, чи можна вести блог про цей список "ніколи не роби"? Мені цікаво бачити інші приклади, які ніколи не будуть частиною мови C # :)
Ілля Риженков

2
@Eric: Я дійсно дуже ціную ваше терпіння зі мною :) Дякую! Дуже інформативний.
Джеймс Данне

12

Бо саме так було вказано мову. Вони не додають ніякої цінності, так навіщо їх включати?

Це також дуже схоже на масиви, набрані неявністю

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


1
Вони не додають ніякого значення в тому сенсі, що повинно бути очевидним, який уміст є, але він порушує послідовність у тому, що тепер у нас є два розрізнені синтаксиси побудови об'єкта, один із необхідними дужками (і вирази аргументу, визначені в комах) і один без .
Джеймс Данн

1
@James Dunne, це насправді дуже схожий на синтаксис неявно введений синтаксис масиву, дивіться мою редагування. Немає ні типу, ні конструктора, і намір очевидний, тому немає необхідності заявляти про це
CaffGeek

7

Це було зроблено для спрощення будівництва об’єктів. Мовні дизайнери не (наскільки мені відомо) спеціально не сказали, чому вони вважають, що це корисно, хоча це прямо вказано на сторінці специфікації C # Версії 3.0 :

Вираз створення об'єкта може опускати список аргументів конструктора та додавання круглих дужок, за умови, що він включає об'єкт або ініціалізатор колекції. Опускання списку аргументів конструктора та додавання дужок є рівнозначним визначенню порожнього списку аргументів.

Я припускаю, що вони вважали, що дужки в цьому випадку не потрібні для того, щоб показати наміри розробника, оскільки ініціалізатор об'єктів показує намір побудувати та встановити властивості об'єкта замість цього.


4

У вашому першому прикладі компілятор робить висновок про те, що ви викликаєте конструктор за замовчуванням (специфікація мови 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
@James Dunne - правильно. Це частина, визначена специфікацією мови. Порожні дужки є зайвими, але ви все одно можете їх надати.
Джастін Нісснер

1

Я не Ерік Ліпперт, тому не можу сказати точно, але я б припустив, що це тому, що порожні дужки не потрібні компілятору для того, щоб зробити висновок про конструкцію ініціалізації. Тому вона стає зайвою інформацією, а не потрібною.


Так, це зайве, але мені просто цікаво, чому раптове введення їх не є обов'язковим? Здається, вона розривається з послідовністю синтаксису мови. Якщо у мене не було відкритої фігурної дужки для позначення блоку ініціалізатора, то це повинен бути незаконним синтаксисом. Смішно, що ви згадуєте містера Ліпперта, я начебто публічно ловляв його відповідь, щоб я та інші отримали користь від простою цікавості. :)
Джеймс Данн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.