Неоднозначность необязательных скобок. Часть 2
В прошлый раз я рассказал о том, почему команда проектировщиков C# 3.0 приняла решение разрешить элизию списка аргументов при вызове конструктора, который использует синтаксис инициализации объектов или коллекций. При этом естественно возникает следующий вопрос:
Раз уж вы этим занимались, так почему вы не сделали все пустые списки аргументы при вызове конструкторов необязательными, даже когда они не используются в инициализаторе?
Хороший вопрос. Хочу прояснить один момент – ваше предложение заключается в том, чтобы сделать список аргументов необязательным в C# 3.0. Я не участвовал в обсуждении вопроса «должна ли быть реализована эта возможность в C# 1.0?»; факт в том, что она не была реализована в C# 1.0, так что это предложение возникло именно в данном контексте.
В прошлый раз я поделился некоторыми мыслями о затратах, связанных с синтаксическим сахаром и заметил, что для того, чтобы реализовать эту особенность, затраты оказываются действительно малы. Однако вы можете удивиться, что затраты на реализацию предложенной вами возможности значительно превосходят затраты на реализацию инициализаторов объектов. Почему? Потому что предложенная особенность приводит к неоднозначности фазы семантического анализа компиляции вероятных программ на C# 2.0.
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
}
}
В C# 2.0 в строке 1 создается новый объект класса C, вызывается его конструктор по умолчанию, а затем вызывается экземплярный метод M нового объекта. В строке 2 создается новый экземпляр B.M и вызывается его конструктор по умолчанию. Если скобки в строке 1 будут необязательными, тогда строка 2 будет неоднозначной. Мы можем найти даже более сложные случаи:
class P
{ class B
{
public class M
{
public static implicit operator D(M m) { return null; }
}
}
delegate D D();
class C : B
{
new public D M(){return null;}
}
static void Main()
{
D d = new C.M;
}
}
Эта программа не является корректной с точки зрения языка C# 2.0, но если мы допустим возможность не указывать скобки, то она станет чрезвычайно неоднозначной. Существует три возможных значения new C.M. Если это new C().M, тогда это преобразование группового метода (method group) M объекта типа C к делегату типа D. Если это new C.M(), тогда это неявное преобразование, определенное пользователем, экземпляра B.M к D. И если это new C().M(), тогда это выполнение метода M экземпляра C, который возвращает null в качестве делегата типа D.
Жуть. Нам нужно будет найти правило разрешения каждой возможной неоднозначности, выдавая при этом предупреждение. (Мы не будет делать это ошибкой, поскольку это может привести к критическим изменениям (breaking change), которые преобразуют ранее корректную программу в некорректную).
Правило должно быть не сложным, а избирательным: по сути скобки могут быть необязательными только тогда, когда это не приводит к неоднозначности. Такое нечеткое правило неприменимо для разработчиков компиляторов. Нам придется анализировать все возможные сценарии, которые приводят к неоднозначностям и написать код в компиляторе, который будет определять их.
Рассмотрев особенность с этой стороны, давайте вернемся назад к тем затратам, о которых я упоминал в прошлый раз. Какие из них возросли на этот раз? Сложные правила приведут к увеличению затрат на проектирование, написание спецификации, разработку, тестирование и документирование. Очень вероятно, что такие сложные правила приведут к проблемам и неожиданному влиянию на другие функции. Возрастают шансы на то, что это приведет к проблемам производительности или корректности таких механизмов IDE, как IntelliSense.
И все ради чего? Практически незаметная выгода для пользователя, которая не добавляет никакой выразительной силы языку, но приводит к безумным граничным сценариям, и крику «Ух ты», когда какой-нибудь бедный человек на него наткнется.
Подобные особенности отбрасываются сразу же и добавляются в список «никогда не делайте этого».
Бог ты мой! Как же вы нашли эти неоднозначности?
Об этом в следующий раз!