Один из хороших случаев использования "Необязательных параметров" в сочетании с "Именованными параметрами" в C# 4.0 заключается в том, что они представляют нам элегантную альтернативу перегрузке методов, когда вы перегружаете метод на основе количества параметров.
Например, вы хотите, чтобы метод foo
вызывался/использовался следующим образом: foo()
, foo(1)
, foo(1,2)
, foo(1,2, "hello")
. С перегрузкой метода вы могли бы реализовать решение следующим образом,
///Base foo method
public void DoFoo(int a, long b, string c)
{
//Do something
}
/// Foo with 2 params only
public void DoFoo(int a, long b)
{
/// ....
DoFoo(a, b, "Hello");
}
public void DoFoo(int a)
{
///....
DoFoo(a, 23, "Hello");
}
.....
С необязательными параметрами в C# 4.0 вы могли бы реализовать случай использования следующим образом,
public void DoFoo(int a = 10, long b = 23, string c = "Hello")
Затем вы могли бы использовать метод следующим образом - Обратите внимание на использование именованного параметра -
DoFoo(c:"Hello There, John Doe")
Этот вызов принимает значение параметра a
как 10 и параметра b
как 23.
Другой вариант этого вызова - обратите внимание, что вам не нужно устанавливать значения параметров в том порядке, в котором они указаны в сигнатуре метода, именованный параметр делает значение явным.
DoFoo(c:"hello again", a:100)
Еще одним преимуществом использования именованного параметра является то, что он значительно улучшает читаемость и, следовательно, сопровождение кода методов с необязательными параметрами.
Обратите внимание, как один метод делает излишним определение трех или более методов в перегрузке методов. Я обнаружил, что это хороший пример использования необязательного параметра в сочетании с именованными параметрами.
Необязательные параметры создают проблемы, когда вы публично раскрываете их как API. Переименование параметра может привести к проблемам. Изменение значения по умолчанию приводит к проблемам (см., например, здесь: Caveats of C# 4.0 optional parameters)
Кроме того, опциональные параметры можно использовать только для констант времени компиляции. Сравните это:
public static void Foo(IEnumerable<string> items = new List<string>()) {}
// Default parameter value for 'items' must be a compile-time constant
с этим
public static void Foo() { Foo(new List<string>());}
public static void Foo(IEnumerable<string> items) {}
//all good
Вот некоторые дополнительные материалы для чтения, когда конструктор с параметрами по умолчаниюне очень хорошо играет с Reflection.
Это почти само собой разумеющееся, но:
Не все языки поддерживают необязательные параметры. Если вы хотите, чтобы ваши библиотеки были дружественными к этим языкам, вам нужно использовать перегрузки.
Конечно, это не проблема даже для большинства магазинов. Но можете поспорить, что именно поэтому Microsoft не использует необязательные параметры в библиотеке базовых классов.
Как насчет третьего варианта: передать экземпляр класса со свойствами, соответствующими различным "необязательным параметрам".
Это дает те же преимущества, что и именованные и необязательные параметры, но я чувствую, что часто это гораздо понятнее. Это дает вам возможность логически группировать параметры, если это необходимо (т.е. с помощью композиции), а также инкапсулировать некоторую базовую проверку.
Кроме того, если вы ожидаете, что клиенты, использующие ваши методы, будут заниматься метапрограммированием (например, строить linq-выражения с использованием ваших методов), я думаю, что сохранение простой сигнатуры метода имеет свои преимущества.
Ни один из них не определенно "лучше" другого. У них обоих есть свое место в написании хорошего кода. Необязательные параметры следует использовать, если параметры могут иметь значение по умолчанию. Перегрузка метода должна использоваться, когда разница в сигнатуре выходит за рамки отсутствия параметров, которые могут иметь значения по умолчанию (например, поведение различается в зависимости от того, какие параметры передаются, а какие остаются по умолчанию).
// this is a good candidate for optional parameters
public void DoSomething(int requiredThing, int nextThing = 12, int lastThing = 0)
// this is not, because it should be one or the other, but not both
public void DoSomething(Stream streamData = null, string stringData = null)
// these are good candidates for overloading
public void DoSomething(Stream data)
public void DoSomething(string data)
// these are no longer good candidates for overloading
public void DoSomething(int firstThing)
{
DoSomething(firstThing, 12);
}
public void DoSomething(int firstThing, int nextThing)
{
DoSomething(firstThing, nextThing, 0);
}
public void DoSomething(int firstThing, int nextThing, int lastThing)
{
...
}
Одно из преимуществ использования необязательных параметров заключается в том, что вам не нужно делать условную проверку в ваших методах, например, если строка была null или пустой, если один из входных параметров был строкой. Поскольку необязательному параметру будет присвоено значение по умолчанию, защитное кодирование будет в значительной степени сокращено.
Именованные параметры дают возможность передавать значения параметров в любом порядке.
Чтобы ответить на ваш первый вопрос,
почему большинство классов библиотеки MSDN используют перегрузка вместо необязательной параметры?
Это для обратной совместимости.
Когда вы открываете проект C # 2, 3.0 или 3.5 в VS2010, он автоматически обновляется.
Только представьте себе неудобства, которые это может создать, если каждую из перегрузок, используемых в проекте, нужно будет преобразовать, чтобы она соответствовала объявлению соответствующего необязательного параметра.
К тому же, как говорится, «зачем чинить то, что не сломано?». Нет необходимости заменять уже работающие перегрузки новыми реализациями.
Необязательные параметры должны быть последними. Поэтому вы не можете добавить дополнительный параметр к этому методу, если он также не является необязательным. Например:
void MyMethod(int value, int otherValue = 0);
Если вы хотите добавить новый параметр к этому методу без перегрузки, он должен быть необязательным. Например, так
void MyMethod(int value, int otherValue = 0, int newParam = 0);
Если он не может быть необязательным, то вы должны использовать перегрузку и удалить необязательное значение для 'otherValue'. Например, так:
void MyMethod(int value, int otherValue = 0);
void MyMethod(int value, int otherValue, int newParam);
Я предполагаю, что вы хотите сохранить порядок следования параметров прежним.
Таким образом, использование необязательных параметров уменьшает количество методов в классе, но ограничивается тем, что они должны быть последними.
Update При вызове методов с необязательными параметрами можно использовать именованные параметры, например, так:
void MyMethod(int value, int otherValue = 0, int newValue = 0);
MyMethod(10, newValue: 10); // Here I omitted the otherValue parameter that defaults to 0
Таким образом, необязательные параметры дают вызывающему больше возможностей.
И последнее. Если вы используете перегрузку методов с одной реализацией, как это:
void MyMethod(int value, int otherValue)
{
// Do the work
}
void MyMethod(int value)
{
MyMethod(value, 0); // Do the defaulting by method overloading
}
Тогда при вызове 'MyMethod', как это:
MyMethod(100);
Получится 2 вызова метода. Но если вы используете необязательные параметры, то существует только одна реализация 'MyMethod' и, следовательно, только один вызов метода.
Я считаю, что они служат разным целям. Необязательные параметры предназначены для случаев, когда вы можете использовать значение по умолчанию для параметра, и основной код будет таким же:
public CreditScore CheckCredit(
bool useHistoricalData = false,
bool useStrongHeuristics = true) {
// ...
}
Перегрузки метода предназначены для случаев, когда у вас есть взаимоисключающие (подмножества) параметров. Обычно это означает, что вам нужно предварительно обработать некоторые параметры или что у вас есть совсем другой код для разных «версий» вашего метода (обратите внимание, что даже в этом случае некоторые параметры могут быть общими, поэтому я упомянул «подмножества» выше) :
public void SendSurvey(IList<Customer> customers, int surveyKey) {
// will loop and call the other one
}
public void SendSurvey(Customer customer, int surveyKey) {
...
}
(Я писал об этом некоторое время назад здесь )