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

(Это вторая часть из серии статей о различных проблемах, связанных с необязательными аргументами в C# 4. Первая часть находится здесь. Эта часть серии появилась благодаря следующему вопросу на StackOverflow.)

В прошлый раз мы увидели, что необязательные параметры, объявленные в методе интерфейса, не должны быть обязательными в классе, реализующем этот метод. Это смущает; почему нельзя потребовать, чтобы реализация метода в классе в точности повторяла объявление необязательного параметра интерфейса?

Потому что в этом случае лекарство хуже болезни.

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

interface IABC
{
  void M(bool x = true);
}
interface IXYZ
{
  void M(bool x = false);
}

class C : IABC, IXYZ
{
  public void M(bool x) {}
}

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

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

Предположим, в вашем коде объявлен интерфейс:

public interface IFoo
{
  void M(int x, string y, bool z);
}

И реализует его сотня разных классов. Потом вы понимаете, что хотите его изменить:

public interface IFoo
{
  void M(int x, string y, bool z = false);
}

И вы, правда, при этом хотите менять сотню разных объявлений и реализаций этого метода? Это кажется весьма обременительным для программиста, но мы можем на это пойти.

Предположим, мы так и сделали. Теперь, давайте предположим, что интерфейс IFoo объявлен не в вашем коде, а находится, например, в сторонней библиотеке. В новой версии библиотеки для этого интерфейса добавлено значение по умолчанию для параметра z. Тогда, при перекомпиляции своего кода, тысяче разных клиентов придется изменить свой код, чтобы он соответствовал этому параметру по умолчанию! Подобное поведение приводит к тому, что добавление параметра по умолчанию потенциально может привести к значительным поломкам (compilation-breaking changes) кода во время компиляции.

Это плохо. Но все, на самом деле, еще хуже. Предположим, вы столкнулись с подобной ситуацией. Интерфейс IFoo предоставляется сторонней библиотекой от FooCorp. Базовый класс Bar предоставляется другой библиотекой от BarCorp.

public class Bar
{
  public void M(int x, string y, bool z) { ... }
}

Обратите внимание, что класс Bar не реализует интерфейс IFoo. И вы хотите в своем коде использовать код FooCorp и BarCorp, следующим образом:

class Mine : Bar, IFoo
{
}

(Это возможно, поскольку метод M класса Bar теперь является членом класса Mine, и, таким образом, класс Mine неявно реализовывает интерфейс IFoo.M.)

Теперь библиотека от FooCorp обновляется, и в методе M появляется значение по умолчанию для параметра z. Вы перекомпилируете класс Mine и компилятор говорит вам, что вы не можете этого сделать, потому что Bar , содержит неверное значение по умолчанию для параметра z. Но ведь не вы являетесь автором класса Bar! И что делать вам, звонить разработчикам из BarCorp и просить им изменить свою библиотеку, потому что в сборку от FooCorp, их конкурента, было добавлено значение по умолчанию формального параметра интерфейса? И что вам делать, если вы получите отказ? Этот метод даже не является виртуальным, так что вы не можете его переопределить. Решение выглядит ужасно:

class Mine : Bar, IFoo
{
  public new void M(int x, string y, bool z = false)
  {
    base.M(x, y, z);
  }
}

А лучше всего, просто не требовать такой избыточности.

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

(Это вторая часть из серии статей о различных сложностях, связанных с необязательными аргументами в C# 4. Первая часть находится здесь.)

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