Я смотрел рассказ Андерса о C # 4.0 и предварительный просмотр C # 5.0 , и это заставило меня задуматься о том, когда в C # будут доступны дополнительные параметры, каким будет рекомендуемый способ объявления методов, для которых не нужно указывать все параметры?
Например, что-то вроде этого FileStream
класса имеет около пятнадцати различных конструкторов, которые могут быть разделены на логические «семейства», например, те, которые находятся ниже, из строки, те, которые находятся в an, IntPtr
и те, что из a 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 будет ли разумнее объединить тесно связанные группы конструкторов и методов в один метод с необязательными параметрами, или есть веская причина придерживаться традиционного механизма с множеством перегрузок?
источник
Когда перегрузка метода обычно выполняет одно и то же с другим числом аргументов, будут использоваться значения по умолчанию.
Когда перегрузка метода выполняет функцию по-разному в зависимости от своих параметров, перегрузка будет продолжать использоваться.
Я использовал optional еще во времена VB6 и с тех пор пропустил его, это уменьшит дублирование XML-комментариев в C #.
источник
Я всегда использовал Delphi с необязательными параметрами. Вместо этого я перешел на использование перегрузок.
Потому что, когда вы переходите к созданию дополнительных перегрузок, вы неизбежно конфликтуете с формой необязательных параметров, и тогда вам все равно придется преобразовать их в необязательные.
И мне нравится идея, что обычно существует один супер- метод, а остальные представляют собой более простые оболочки для этого.
источник
Foo(A, B, C)
требуетFoo(A)
,Foo(B)
,Foo(C)
,Foo(A, B)
,Foo(A, C)
,Foo(B, C)
.Я обязательно буду использовать опцию необязательных параметров 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
. Таким образом, следует избегать использования дополнительных параметров для вещей, которые могут измениться.источник
void Foo(int value) … void Foo() { Foo(42); }
. Снаружи вызывающий не знает, какое значение по умолчанию используется и когда оно может измениться; для этого нужно будет следить за письменной документацией. Значения по умолчанию для дополнительных параметров можно рассматривать как просто: документация в коде, что такое значение по умолчанию.Можно спорить о том, следует ли использовать необязательные аргументы или перегрузки или нет, но, что наиболее важно, у каждого есть своя собственная область, где они незаменимы.
Необязательные аргументы, когда они используются в сочетании с именованными аргументами, чрезвычайно полезны в сочетании с некоторыми длинными списками аргументов со всеми дополнительными параметрами вызовов COM.
Перегрузки чрезвычайно полезны, когда метод может работать со многими различными типами аргументов (только один из примеров) и, например, выполняет внутренние преобразования; вы просто загружаете его любым типом данных, который имеет смысл (который принимается некоторой существующей перегрузкой). Этого не победить необязательными аргументами.
источник
Я с нетерпением жду дополнительных параметров, потому что они сохраняют значения по умолчанию, которые ближе к методу. Таким образом, вместо десятков строк для перегрузок, которые просто вызывают «расширенный» метод, вы просто определяете метод один раз и можете видеть, какие дополнительные параметры используются по умолчанию в сигнатуре метода. Я лучше посмотрю на:
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 перегрузками все может очень быстро переполниться.
источник
Один из моих любимых аспектов необязательных параметров заключается в том, что вы видите, что происходит с вашими параметрами, если вы их не предоставляете, даже не переходя к определению метода. Visual Studio просто покажет вам значение по умолчанию для параметра, когда вы введете имя метода. При использовании метода перегрузки вы застряли либо на чтении документации (если она даже есть), либо при прямом переходе к определению метода (если доступно) и к методу, который перегрузка переносит.
В частности: объем документации может быстро увеличиваться с увеличением количества перегрузок, и вы, вероятно, в конечном итоге скопируете уже существующие комментарии из существующих перегрузок. Это очень раздражает, так как не дает никакой ценности и нарушает принцип DRY ). С другой стороны, с необязательным параметром есть ровно одно место, где все параметры задокументированы, и вы видите их значение, а также их значения по умолчанию при вводе.
И последнее, но не менее важное: если вы являетесь потребителем API, у вас может даже не быть возможности проверить детали реализации (если у вас нет исходного кода) и, следовательно, у вас нет возможности увидеть, какой супер-метод перегружен. заворачивают. Таким образом, вы застряли в чтении документа и надеетесь, что там перечислены все значения по умолчанию, но это не всегда так.
Конечно, это не ответ, который касается всех аспектов, но я думаю, что он добавляет один, который до сих пор не рассматривался.
источник
Хотя это (предположительно?) Два концептуально эквивалентных способа моделирования вашего 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
будет иметь непредвиденный эффект, событие больше не будет считаться критическим.Это может привести к незначительному дефекту, поскольку не будет ошибок компиляции или выполнения (в отличие от некоторых других предупреждений о дополнительных параметрах, таких как this или this ).
Обратите внимание, что существует множество форм этой проблемы. Еще одна форма изложена здесь .
Отметим также , что строго с использованием именованных параметров при вызове метода позволит избежать проблемы, такие , как например:
HandleError("Disk is full", silent:false)
. Однако нецелесообразно предполагать, что все другие разработчики (или пользователи общедоступного API) сделают это.По этим причинам я бы избегал использования дополнительных параметров в общедоступном API (или даже в общедоступном методе, если он может широко использоваться), если нет других веских соображений.
источник
Оба параметра Optional и Method overload имеют свои преимущества или недостатки. Выбор между ними зависит от вашего предпочтения.
Необязательный параметр: доступен только в .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; }
Таким образом, мы даже защитили класс от звонков с нулевой скидкой. Этот звонок будет означать, что звонящий думает, что есть скидка, но на самом деле скидки нет вообще. Такое недоразумение может легко вызвать ошибку.
В подобных случаях я предпочитаю не иметь необязательных параметров, а заставить вызывающего абонента явно выбирать сценарий выполнения, соответствующий его текущей ситуации.
Ситуация очень похожа на параметры, которые могут быть нулевыми. Это также плохая идея, когда реализация сводится к таким утверждениям, как
if (x == null)
.Вы можете найти подробный анализ по этим ссылкам: Избегание дополнительных параметров и Избегание нулевых параметров
источник
Чтобы добавить простую задачу, когда использовать перегрузку вместо дополнительных:
Если у вас есть несколько параметров, которые имеют смысл только вместе, не вводите для них дополнительные параметры.
Или, в более общем плане, всякий раз, когда сигнатуры ваших методов включают шаблоны использования, которые не имеют смысла, ограничьте количество перестановок возможных вызовов. Например, с использованием перегрузок вместо дополнительных (кстати, это правило также выполняется, когда у вас есть несколько параметров одного и того же типа данных; здесь могут помочь такие устройства, как заводские методы или пользовательские типы данных).
Пример:
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);
источник