Share via


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

В C# 4.0 мы добавили «необязательные аргументы». Это означает, что вы можете указать в объявлении параметров метода, что если некоторые аргументы не заданы, то они будут заменены константами:

void M(int x = 123, int y = 456) { }

Метод M может быть вызван следующим образом: M(), M(0) и M(0, 1). В первых двух вариантах будут осуществлены вызовы M(123, 456) и M(0, 456), соответственно.

Это была неоднозначная возможность для команды разработчиков языка, и мы отказывались добавлять ее в язык в течение десяти лет, несмотря на многочисленные просьбы и примеры аналогичных возможностей в других языках, таких как С++ и Visual Basic. Несмотря на очевидное удобство этого способа, он идет вместе с достаточно высокой ценой, обусловленной замысловатыми сложностями, с которыми команда разработчиков языка должна была как-то справиться, и с которыми иногда сталкиваются пользователи. И я хочу рассказать о некоторых подобных моментах.

Прежде всего, несколько из них связаны с интерфейсами. Давайте посмотрим, как необязательные параметры взаимодействуют с «неявной» (implicit) реализацией интерфейсов.

interface IFoo
{
void M(int x = 123);
}
class Foo : IFoo
{
public void M(int x){}
}

В данном случае метод M класса Foo неявно реализует метод M интерфейса IFoo. Что произойдет в следующем случае:

Foo foo = new Foo();
foo.M();

Будет выдана ошибка во время определения перегруженного метода. Только потому, что определение метода M класса Foo неявно реализует метод IFoo.M не означает, что алгоритм разрешения перегрузки об этом узнает и воспользуется им. На самом деле, если бы он поступал именно таким образом, то все было бы еще сложнее. Предположим у нас есть следующий код:

interface IFoo
{
void M(int x = 123);
}
interface IBar
{
void M(int x = 456);
}
class Foo : IFoo, IBar
{
public void M(int x){}
}

Метод M класса Foo неявно реализует оба интерфейса; а значение какого параметра по умолчанию должно использоваться в этом случае?

Здесь применяется очень простое правило: при поиске метода некоторого класса, во время разрешения перегрузки учитываются только «фактические» параметры, определенные в этом классе Необязательные параметры интерфейса учитываются только при вызове метода через интерфейс:

IFoo foo = new Foo();
foo.M(); // Корректно

Именно поэтому при использовании необязательных параметров во время явной реализации интерфейса компилятор выдает предупреждение:

interface IAbc
{
public void M(int x) {}
}
class Abc : IAbc
{
void IAbc.M(int x = 123) {}
}

Компилятор предупреждает о том, что значение параметра по умолчанию не имеет никакого смысла. Поскольку мы можем вызвать этот метод только через интерфейс, то, как компилятор при вызове метода сможет определить, в какой реализаций этого интерфейса нужно искать значение параметра по умолчанию? Основной смысл интерфейсов заключается в отделении метода от его реализации, так что узнать об этом нет никакой возможности.

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

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