В Инфуфа веруем: диалог

Пользователь: Оператор typeof(T) в C#, по существу, означает «компилятор, сгенерируй некий код, который при исполнении породит для меня объект, представляющий тип, который ты связываешь с именем T». Было бы здорово иметь похожие операторы, принимающие имена, скажем, методов и возвращающие другие объекты метаданных, с информацией о методах, свойствах и так далее. Это был бы более приятный синтаксис, чем передача корявых строк, типов и флагов привязки в различные API рефлексии для получения этой информации.

Эрик: Я согласен, это был бы прекрасный сахар. Эта идея поднимается на собраниях по проектированию C# уже почти десять лет. Мы называем предлагаемый оператор «infoof», поскольку он возвращал бы произвольную информацию о метаданных. Мы капризно настаиваем, что его нужно произносить «инфуф», а не «инфооф».

Пользователь: Ну так реализуйте его уже, раз вас просят об этом уже десять лет. Чего вы ждете?

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

Во-вторых, эта возможность на первый взгляд кажется простой, но на деле у неё весьма большая «площадь дизайна» и она потребует добавления заметного количества нового синтаксиса в язык.

В-третьих, она не даёт ничего такого, чего вы не могли бы добиться существующим кодом; это не необходимо, а просто приятно.

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

Пользователь: А можно поподробнее насчёт проблем?

Эрик: Давайте начнем с простой. Предположим у вас есть infoof(Bar), где Bar – перегруженный метод. Предположим, что есть конкретный Bar, по которому вы хотите получить информацию. Это проблема разрешения перегрузок. Каким образом вы скажете алгоритму разрешения перегрузок, какую именно вы хотите выбрать? Существующий алгоритм разрешения перегрузок требует либо выражений для аргументов, либо, как минимум, типов аргументов.

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

Пользователь: Ну, тогда всё будет прямолинейно. infoof(Bar(int, int)), готово.

Эрик: Я заметил, что вы только что ввели новый синтаксис; нигде в C# мы раньше не использовали список типов в скобках, разделённый запятыми. Впрочем, синтаксический разбор такого совсем не труден.

Пользователь: Вот именно. Так что давайте уже.

Эрик: Ладно, умник, а как насчет этого странно знакомого случая?

class C<T>
{
  public void Bar(int x, T y) {}
  public void Bar(T x, int y) {}
  public void Bar(int x, int y) {}
}
class D : C<int>
{ …

У тебя где-то в D есть обращение к infoof(Bar(int, int)). Там три кандидата. Кого ты выберешь?

Пользователь: Ну, а какой выбрало бы разрешение перегрузок, если бы ты вызвал Bar(10, 10)? Правила разрешения неоднозначностей предписывают выбирать необобщённую версию в такой жуткой ситуации.

Эрик: Отлично. За исключением того, что это не то, чего я хотел. Мне была нужна информация о второй версии. Если твой предлагаемый синтаксис выбирает только одну, то тебе нужно дать мне синтаксис для выбора остальных.

Пользователь: Хмм. Это трудно, потому что разрешение перегрузок фактически не даёт способа заставить вызвать один из двух методов с обобщенным параметром в этом противоестественном случае, так что нужно изобрести какой-то механизм мощнее, чем разрешение перегрузок, если хочется, чтобы эта фича позволяла получать информацию о любом методе.

Но это – нарочно притянутый пограничный случай; просто выбрось его. Не надо поддерживать этот сценарий.

Эрик: Вот, теперь ты начинаешь понимать, что обычно происходит на собраниях по проектированию! Очень легко спроектировать конструкцию, которая покрывает, как вначале кажется, 80% случаев. А потом ты обнаруживаешь, что еще 80% случаев непокрыты, и либо ты тратишь на фичу 160% отведённого бюджета, либо имеешь в результате запутанную, противоречивую, и слабую фичу, либо и то и другое сразу.

И откуда нам знать, какие сценарии выбрасывать? Мы должны спроектировать конструкцию, которая делает что-то в каждом возможном случае, даже если это «что-то» – выдать ошибку. Так что мы как минимум должны потратить время на рассмотрение всех мыслимых случаев во время проектирования. А рассматривать придется много причудливых вариантов!

Пользователь: Типа каких?

Эрик: Ну прямо навскидку, вот несколько штук. (1) Как ты укажешь без неоднозначности, что нужна информация о методе, реализующем конкретную явную реализацию интерфейса? (2) Что если бы разрешение перегрузок пропустило некоторый метод, потому что он недоступен? Вполне законно получать информацию о методах, которые недоступны; метаданные всегда публичны, даже если они описывают приватные детали. Нужно ли нам запретить получение приватных метаданных, сделав конструкцию слабой, или мы должны его разрешить, заставив infoof использовать слегка другой алгоритм разрешения перегрузок, чем в остальном C#? (3) Как указать, что тебе нужна информация о, скажем, методе установки значения индексера, или методе получения значения свойства, или методе добавления обработчика события?

В избытке есть и еще более неуклюжие пограничные случаи.

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

Эрик: Конечно. Ни одна из этих проблем не является неразрешимой. Но почти все они требуют тщательного проектирования и нового синтаксиса, и вскоре кончится тем, что мы будем тратить большую часть нашего бюджета по проектированию, реализации и тестированию на этот тривиальный «сахар» возможность;бюджета, который мы могли бы потратить на более ценные возможности, которые решают реальные проблемы.

Пользователь: Ну, а как насчёт сделать лёгкие 80%, а «остальные 80%» объявить ошибками? Конечно, усечённая возможность слабее, и, возможно, плохо согласована, но кое-что лучше, чем ничего, и будет дешевле сделать что попроще.

Эрик: Зачастую это ничего не удешевляет. Каждый раз, как мы объявляем пограничный случай ошибкой, нам нужно тщательно указать, в каком именно случае возникает ошибка, чтобы мы могли корректно его реализовать и надёжно протестировать. Каждый ошибочный случай всё еще расходует бюджет на проектирование, реализацию и тестирование, который можно было бы потратить на что-то другое.

Нам бы пришлось придумать осмысленное сообщение об ошибке, локализовать его на я-не-знаю-сколько языков, реализовать код, который обнаруживает вычурные сценарии и выдаёт правильную ошибку, протестировать ошибочные случаи и задокументировать их. И нам бы понадобилось бороться со всеми пользователями, которые бы настаивали на каждом ошибочном случае и требовали, чтобы он работал в их конкретном сценарии.

Попытки ограничить конструкцию так, чтобы её уменьшить, добавляют работы, и возможно, что в перспективе они добавят больше работы, чем простая реализация полной версии конструкции с самого начала. Фич задаром не бывает.

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

Эрик: Тебе стоило сказать это с самого начала! Метод, удобно названный GetCurrentMethod, делает именно это.

Пользователь: Так ты предлагаешь мне не infoof?

Эрик: Это классная штука, и практически каждый участник процесса проектирования хотел бы, чтобы мы сумели ее сделать, но есть хорошие практические причины, по которым мы решили от нее отказаться. Если придет такой день, когда проектирование и реализация её станет лучшим способом потратить наш ограниченный бюджет, мы это сделаем. А до тех пор – используйте рефлексию.

оригинал статьи