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

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

Например, что-то вроде класса 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, я гипотетически говорю об этом типе ситуации].

Как вы думаете? Из С# 4.0 будет ли более целесообразным создание тесно связанных групп конструкторов и методов с единственным методом с необязательными параметрами или есть веская причина придерживаться традиционного механизма многогрузов?

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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 перегрузками, вещи могут быстро переполняться.

Ответ 7

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

Дополнительные аргументы при использовании в сочетании с именованными аргументами чрезвычайно полезны в сочетании с некоторыми длинными аргументами-списками со всеми возможными вызовами COM.

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

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

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

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

Пример:

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

Ответ 12

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

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

    decimal basePrice = CalculateBasePrice(productName);

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

Дисконтный параметр здесь используется для подачи оператора 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).

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