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