Необязательные аргументы с обеих сторон

Прежде чем переходить к сегодняшней теме, небольшое дополнение к моему прошлогоднему сообщению о работе в Рослине. Мы получили множество хороших предложений и кое-кого уже взяли на работу, но у нас все еще есть открытые вакансии, как в команде Roslyn, так и в более крупной команде Visual Studio. Подробности можно посмотреть в этом сообщении в блоге Visual Studio. Напомню еще раз, не присылайте резюме мне напрямую; отправляйте их через наш специальный сайт, посвященный работе. Спасибо!


Вот вопрос, который я недавно получил от одного из читателей: как правильно проанализировать следующую маленькую проблему с необязательными аргументами?

 static void F(string ham, object jam = null) { }
static void F(string spam, string ham, object jam = null) { }
...
F("meat product", null);

Разработчик этого странного кода видимо пытался создать необязательные параметры с обеих сторон; идея в том, чтобы параметры spam и jam были необязательными, но параметр ham был всегда.

Какую из перегруженных версий метода выберет компилятор и почему? Посмотрим, сможете ли вы разобраться в этом самостоятельно.

.

.

.

.

.

.

.

.

.

.

.

.

.

Будет выбран второй метод. (Вы удивлены?)

И причина этого очень простая. У алгоритм разрешения перегрузки методов есть два кандидата и он должен выбрать лучший из них на основе преобразований аргументов к типам формальных параметров. Посмотрите на первый аргумент. Он будет преобразован либо к типу string либо к типу … string. Хм… Мы ничего не можем выбрать на основании этого аргумента, так что давайте его игнорировать.

Теперь давайте рассмотрим второй аргумент. Если мы выбираем первую перегрузку, то мы преобразовываем null к типу object, если же мы выбираем вторую перегрузку – то к типу string. Очевидно, что тип string является более конкретным типом, чем object; каждый объект типа string также является объектом типа object, но не каждый объект типа object является объектом типа string. Мы всегда стараемся выбрать наиболее конкретную перегруженную версию метода, поэтому мы выбираем вторую перегрузку и вызываем

 F("meat product", null, null);

Мораль этой истории, как я уже не однократно упоминал, следующая: если то, что вы делаете приводит к проблемам – просто не делайте этого! Не пытайтесь добавить необязательные параметры с двух сторон; это, черт побери, сбивает с толку, особенно когда существует неоднозначность типов, как в этом случае. Достаточно просто создать один метод с двумя необязательными параметрами в конце списка параметров:

 static void F(string ham, string spam = null, object jam = null) { }

При наличии только лишь одного метода становится ясно, какой аргумент, какому параметру соответствует.

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

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