Должны ли вы объявлять методы, использующие перегрузки или необязательные параметры в C # 4.0?

StackOverflow https://stackoverflow.com/questions/251868

Вопрос

Я наблюдал Выступление Андерса о 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, будет ли более разумным сделать тесно связанные группы конструкторов и методов единым методом с необязательными параметрами, или есть веская причина придерживаться традиционного механизма множественной перегрузки?

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

Я определенно буду использовать опцию необязательных параметров 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, но я не был бы шокирован, узнав, что компилятор просто создает для вас перегрузки.

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

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

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

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)
{
}

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

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

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

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

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

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

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

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

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

Исходный код

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 будет иметь непреднамеренный эффект, событие больше не будет рассматриваться как критическое.

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

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

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

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

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

Необязательный параметр: доступно только в .Net 4.0. необязательный параметр уменьшить размер вашего кода. Вы не можете определить и изменить параметр

перегруженные методы: Вы можете определить параметры Out и ref. Размер кода будет увеличиваться, но перегруженные методы легко понять.

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

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Параметр Discount здесь используется для подачи оператора if-then-else. Существует полиморфизм, который не был распознан, а затем он был реализован как оператор if-then-else. В таких случаях гораздо лучше разделить два потока управления на два независимых метода:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

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

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

Ситуация очень похожа на наличие параметров, которые могут быть нулевыми. Это в равной степени плохая идея, когда реализация сводится к операторам вроде if (x == null) .

Подробный анализ можно найти по следующим ссылкам: Избегание необязательных параметров и Избегание пустых параметров

Хотя они (предположительно?) являются двумя концептуально эквивалентными способами, доступными вам для моделирования API с нуля, к сожалению, у них есть небольшая разница, когда вам нужно рассмотреть обратную совместимость во время выполнения для ваших старых клиентов в дикой природе. Мой коллега (спасибо Брент!) Указал мне на это замечательный пост: проблемы с версиями с дополнительными аргументами . Некоторые цитаты из этого:

  

Причина, по которой дополнительные параметры были введены в C # 4 в   На первом месте была поддержка COM взаимодействия. Вот и все. И теперь мы   узнать о всех последствиях этого факта. Если у тебя есть   метод с необязательными параметрами, вы никогда не можете добавить перегрузку с   дополнительные необязательные параметры из-за боязни вызвать компиляцию   переломный момент И вы никогда не сможете удалить существующую перегрузку, так как   это всегда было изменением во время выполнения. Вы в значительной степени нуждаетесь   относиться к нему как к интерфейсу. Ваш единственный выход в этом случае заключается в   написать новый метод с новым именем. Так что знайте об этом, если вы планируете   используйте необязательные аргументы в своих API.

Чтобы без проблем указать, когда использовать перегрузку вместо опций:

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

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

Пример:

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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top