Сложности с необязательными параметрами. Часть 3

(Это вторая часть из серии сообщений о сложностях с необязательными параметрами в языке C# 4; вторая часть находится здесь).

Многие люди считают, что следующий код:

void M(string x, bool y = false) { ... некоторый код ... }

На самом деле является синтаксическим сахаром для следующего привычного кода на языке C#:

void M(string x) { M(x, false); }
void M(string x, bool y) { ... некоторый код ... }

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

M("hello");

заменяется на:

M("hello", false);

Было бы весьма странно, если бы мы выполняли замену в месте объявления, тогда что бы нам пришлось делать в этом случае?

void N(bool a1 = false, bool a2 = false) { ... некоторый код ... }

Очевидно, мы не можем генерировать следующие варианты:

void N() { N(false, false); }
void N(bool a1) { N(a1, false); }
void N(bool a2) { N(false, a2); }
void N(bool a1, bool a2) { ... некоторый код ... }

Поскольку в этом случае мы получаем два метода с одинаковой сигнатурой. Но зачем вообще нам нужно генерировать метод, принимающий параметр «a2»? Потому что помимо необязательных параметров, мы еще добавили именованные параметры. Кто-то может вызвать метод N следующим образом:

N(a2: true);

Именно поэтому, нам нужно вносить изменения в месте вызова, а не в вызываемом методе.

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

M("hello");

И такой:

Action<string> action = (string s)=>{M(s);};

Вы не можете написать такой:

Action<string> action = M;

Поскольку сигнатура метода M не совпадает с сигнатурой типа делегата; делегат ожидает метод, принимающий строку, но вы передаете метод, принимающий строку и bool. Где должен располагаться код, устанавливающий нужное значение булевого параметра? В этом коде нет места вызова (call site), а значение по умолчанию устанавливается именно в месте вызова.

Аналогично, вы не можете написать следующий код:

class B
{
  public virtual void M(string x, bool y = false) {}
}
class D : B
{
  public override void M(string x) {}
}

Или такой:

class D : B
{
  public override void M(string x, bool y = false, int z = 123) {}
}

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

В следующий раз: дополнительные последствия изменения мест вызова.

(Это вторая часть из серии материалов о сложностях с необязательными параметрами в языке C# 4; вторая часть находится здесь).

Оригинал статьи