Следует ли объявлять методы с использованием перегрузок или необязательных параметров в C# 4.0?



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

691   13  

13 ответов:

Я бы считал следующее:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

исходный код

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

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

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

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

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

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

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

перегруженных методов: Вы можете определить параметры 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;

}

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

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

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

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

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

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

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

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

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

пример:

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);

Comments

    Ничего не найдено.