Должны ли вы объявлять методы, используя перегрузки или необязательные параметры в C # 4.0? - PullRequest
90 голосов
/ 31 октября 2008

Я смотрел разговор Андерса о C # 4.0 и предварительный просмотр C # 5.0 , и это заставило меня задуматься о том, когда в C # доступны дополнительные параметры, что будет рекомендуемым способом объявления методов, которые не нужны все указанные параметры?

Например, что-то вроде FileStream класса имеет около пятнадцати различных конструкторов, которые можно разделить на логические «семейства», например. те, что ниже от строки, те, что из IntPtr, и те, что из SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

Мне кажется, что этот тип шаблона можно упростить, если вместо него использовать три конструктора и использовать необязательные параметры для параметров по умолчанию, что сделает различные семейства конструкторов более четкими [примечание: я знаю это изменение не будет сделано в BCL, я говорю гипотетически для такого типа ситуации].

Что вы думаете? Начиная с C # 4.0, будет ли разумнее делать тесно связанные группы конструкторов и методов единым методом с необязательными параметрами или есть веская причина придерживаться традиционного механизма многократной перегрузки?

Ответы [ 13 ]

113 голосов
/ 31 октября 2008

Я бы рассмотрел следующее:

  • Вам нужен код для языков, которые не поддерживают необязательные параметры? Если это так, рассмотрите возможность включения перегрузок.
  • Есть ли в вашей команде члены, которые яростно выступают против необязательных параметров? (Иногда легче принять решение, которое вам не нравится, чем спорить по делу.)
  • Вы уверены, что ваши настройки по умолчанию не изменятся между сборками вашего кода, или, если они могут, ваши абоненты будут в порядке с этим?

Я не проверял, как будут работать настройки по умолчанию, но я бы предположил, что значения по умолчанию будут вставлены в вызывающий код, почти так же, как ссылки на поля const. Обычно это нормально - изменения значения по умолчанию в любом случае довольно значительны, но это то, что нужно учитывать.

17 голосов
/ 31 октября 2008

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

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

Я использовал опционально в VB6 дни и с тех пор пропустил его, это значительно уменьшит дублирование XML-комментариев в C #.

11 голосов
/ 24 июня 2009

Я всегда использовал Delphi с необязательными параметрами. вместо этого я перешел на использование перегрузок.

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

И мне нравится идея, что обычно есть один супер метод, а остальные - более простые обертки вокруг этого.

7 голосов
/ 24 июня 2009

Я определенно буду использовать опцию необязательных параметров 4.0. Избавляется от смешного ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... и помещает значения туда, где их может видеть вызывающая сторона ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Гораздо проще и гораздо меньше ошибок. Я действительно видел это как ошибку в случае перегрузки ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

Я еще не играл с компилятором 4.0, но я не был бы шокирован, узнав, что компилятор просто создает для вас перегрузки.

6 голосов
/ 21 января 2013

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

Существенным следствием привязки необязательных параметров на сайте вызова является то, что им будут назначены значения в зависимости от версии целевого кода, доступной для компилятора. Если сборка Foo имеет метод Boo(int) со значением по умолчанию 5, а сборка Bar содержит вызов Foo.Boo(), компилятор обработает его как Foo.Boo(5). Если значение по умолчанию изменено на 6, а сборка Foo перекомпилирована, Bar будет продолжать вызывать Foo.Boo(5), пока не будет перекомпилирована с этой новой версией Foo. Поэтому следует избегать использования необязательных параметров для вещей, которые могут измениться.

3 голосов
/ 08 марта 2016

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

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

И последнее, но не менее важное: если вы являетесь пользователем API, у вас может даже не быть возможности проверить детали реализации (если у вас нет исходного кода), и, следовательно, у вас нет возможности увидеть, какой метод super перегруженные обертывают. Таким образом, вы застряли с чтением документа и надеетесь, что все значения по умолчанию перечислены там, но это не всегда так.

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

3 голосов
/ 21 мая 2010

Можно утверждать, должны ли использоваться необязательные аргументы или перегрузки, но самое главное, у каждого есть своя собственная область, где они незаменимы.

Необязательные аргументы, используемые в сочетании с именованными аргументами, чрезвычайно полезны в сочетании с некоторыми необязательными списками аргументов со всеми аргументами COM-вызовов.

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

3 голосов
/ 31 октября 2008

Я с нетерпением жду дополнительных параметров, потому что они сохраняют значения по умолчанию ближе к методу. Таким образом, вместо десятков строк для перегрузок, которые просто вызывают «расширенный» метод, вы просто определяете метод один раз и видите, какие необязательные параметры по умолчанию используются в сигнатуре метода. Я бы лучше посмотрел на:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

Вместо этого:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Очевидно, этот пример действительно прост, но в случае с 5 перегрузками в OP все может быть очень быстро.

1 голос
/ 08 марта 2018

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

Исходный код

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

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

HandleError("Disk is full", false);

Здесь событие не является бесшумным и рассматривается как критическое.

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

После рефакторинга

Первый вызов все еще компилируется, и, скажем, он пропускает рефакторинг без изменений:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Теперь false будет иметь непреднамеренный эффект, событие больше не будет рассматриваться как критическое.

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

Обратите внимание, что есть много форм этой же проблемы. Еще одна форма обозначена здесь .

Обратите внимание, что строгое использование именованных параметров при вызове метода позволит избежать этой проблемы, например: HandleError("Disk is full", silent:false). Однако может быть нецелесообразно предполагать, что все другие разработчики (или пользователи общедоступного API) будут делать это.

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

0 голосов
/ 08 декабря 2017

Чтобы добавить простую задачу при использовании перегрузки вместо дополнительных:

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

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

Пример:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
...