Подтвердить что ты не робот

Почему С# допускает неоднозначные вызовы функций через необязательные аргументы?

Сегодня я наткнулся на это, и я удивлен, что раньше этого не заметил. Учитывая простую программу на С#, похожую на следующую:

public class Program
{
    public static void Main(string[] args)
    {
        Method(); // Called the method with no arguments.
        Method("a string"); // Called the method with a string.

        Console.ReadLine();
    }

    public static void Method()
    {
        Console.WriteLine("Called the method with no arguments.");
    }

    public static void Method(string aString = "a string")
    {
        Console.WriteLine("Called the method with a string.");
    }
}

Вы получаете вывод, указанный в комментариях для каждого вызова метода.

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

Насколько я могу судить, функция перегрузки с сигнатурой, которая отличается от другой перегрузки только наличием дополнительного необязательного аргумента, предлагает не что иное, как если бы аргумент (и все предыдущие аргументы) просто требовался.

Одна вещь, которую он делает, позволяет программисту (который, вероятно, не уделяет достаточного внимания) думать, что они вызывают разную перегрузку той, которая есть на самом деле.

Я предполагаю, что это довольно необычный случай, и ответ на вопрос, почему это разрешено, может быть просто потому, что просто не стоит сложности, чтобы запретить его, но есть ли еще одна причина, по которой С# позволяет перегрузкам функций отличаться от других только посредством один дополнительный необязательный аргумент?

4b9b3361

Ответ 1

Его точка, на которую мог ответить Эрик Липперт, привела меня к этому https://meta.stackoverflow.com/a/323382/1880663, и это звучит так, будто мой вопрос будет только раздражать его. Я попытаюсь перефразировать его, чтобы уточнить, что я спрашиваю о дизайне языка и что я не ищу ссылку спецификации

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


Комментарий к вашему вопросу, отправленный Гансом, правильный. Команда разработчиков языка хорошо знала проблему, которую вы поднимаете, и это далеко не единственная потенциальная двусмысленность, созданная необязательными/именованными аргументами. Мы рассмотрели множество сценариев в течение длительного времени и разработали эту функцию как можно более тщательно, чтобы смягчить потенциальные проблемы.

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

Я не собираюсь перефразировать то, что было десятки часов дебатов; позвольте мне просто дать вам высокие очки.

Основным мотивирующим сценарием для этой функции был, как отмечает Ханс, популярный спрос, особенно от разработчиков, которые используют С# с Office. (И полное раскрытие, как парень из команды, которая написала модель программирования С# для Word и Excel, прежде чем я присоединилась к команде С#, я был буквально первым, кто ее просил, ирония, что мне тогда пришлось реализовать эту сложную функцию пара лет спустя не была потеряна для меня.) Модели объектов Office были разработаны для использования с Visual Basic, языком, который уже давно имеет опциональную/именованную поддержку параметров.

С# 4 может показаться немного "тонким" выпуском с точки зрения очевидных функций. Это связано с тем, что большая часть работы, выполненной в этом выпуске, была инфраструктурой для обеспечения более полной функциональной совместимости с объектными моделями, предназначенными для динамических языков. Функция динамического ввода является очевидной, но было добавлено множество других небольших функций, которые объединяются вместе, чтобы упростить работу с динамическими и устаревшими объектами COM-объектов. Именованные/необязательные аргументы были только одним из них.

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

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

Кроме того, честно говоря, если вы пишете код, в котором у вас есть два метода, называемые те же самые вещи, которые делают что-то совершенно другое, в зависимости от того, на кого вы звоните, у вас уже есть большая проблема с дизайном на ваших руках! Сначала исправьте эту проблему, вместо того, чтобы беспокоиться о том, что кто-то случайно вызовет неправильный метод; сделайте так, чтобы любой из методов был правильным.

Ответ 2

Это поведение указано Microsoft в MSDN. Посмотрите Именованные и необязательные аргументы (Руководство по программированию на С#).

Если два кандидата считаются одинаково хорошими, предпочтение отдается кандидату, который не имеет необязательных параметров, для которых аргументы были опущены в вызове. Это является следствием общего предпочтения в разрешении перегрузки для кандидатов, у которых меньше параметров.

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

UPDATE

Я удивлен, и у Jon Skeet нет настоящей эксплоатации, почему они сделали это так.

Ответ 3

Я думаю, что этот вопрос в основном сводится к тому, как эти подписи представлены промежуточным языком. Обратите внимание, что подписи обеих перегрузок не равны! Второй способ имеет такую ​​подпись:

.method public hidebysig static void Method([opt] string aString) cil managed
{
    .param [1] = string('a string')
    // ...
}

В IL подпись метода отличается. Он берет строку, которая помечена как необязательная. Это изменяет поведение способа инициализации параметра, но не меняет присутствие этого параметра.

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

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

Там также есть хороший ответ Chuck Skeet на этот вопрос.