Дополнительные параметры для интерфейсов

Использование С# 4.0 — создание интерфейса и класса, реализующего интерфейс. Я хочу объявить необязательный параметр в интерфейсе и отразить его в классе. Итак, у меня есть следующее:

 public interface IFoo
 {
      void Bar(int i, int j=0);
 }

 public class Foo
 {
      void Bar(int i, int j=0) { // do stuff }
 }

Это компилируется, но выглядит неправильно. Интерфейс должен иметь необязательные параметры, поскольку в противном случае он не будет корректно отражаться в сигнатуре метода интерфейса.

Должен ли я пропустить необязательный параметр и просто использовать тип, допускающий значение NULL? Или это будет работать как задумано без побочных эффектов или последствий?


person bryanjonker    schedule 02.04.2010    source источник
comment
Комментарии Мартина ниже дают подробный пример различных ловушек, и я бы хотел, чтобы компилятор помечал несоответствующие аргументы по умолчанию. Тем не менее, я использую это в своем коде, потому что это указывает на мое намерение как разработчика как на уровне интерфейса, так и на уровне реализации, чтобы другие разработчики могли его увидеть.   -  person Pete Magsig    schedule 09.07.2014


Ответы (5)


Вы можете рассмотреть альтернативу предварительным необязательным параметрам:

public interface IFoo
{
    void Bar(int i, int j);
}

public static class FooOptionalExtensions
{
    public static void Bar(this IFoo foo, int i)
    {
        foo.Bar(i, 0);
    }
}

Если вам не нравится внешний вид новой языковой функции, вам не нужно ее использовать.

person pdr    schedule 02.04.2010
comment
Не стесняйтесь предоставить дополнительное объяснение того, как это работает, или, возможно, ссылку на то, что вы называете альтернативой предварительным необязательным параметрам. Может помочь будущим пользователям! :] - person Danny Bullis; 21.10.2016
comment
Конечно, действительно старый способ сделать это (предшествующий C# 3, в котором были введены методы расширения) заключался в использовании перегрузки методов как в классе, так и в интерфейсе. Если у вас есть доступ к коду класса, перегрузка, вероятно, лучше, чем метод расширения, так как он сохраняет код в одном месте. - person Martin Brown; 02.03.2017

Что действительно странно, так это то, что значение, которое вы указываете для необязательного параметра в интерфейсе, на самом деле имеет значение. Я полагаю, вы должны задаться вопросом, является ли значение деталью интерфейса или деталью реализации. Я бы сказал второе, но все ведет себя как первое. Например, следующий код выводит 1 0 2 5 3 7.

// Output:
// 1 0
// 2 5
// 3 7
namespace ScrapCSConsole
{
    using System;

    interface IMyTest
    {
        void MyTestMethod(int notOptional, int optional = 5);
    }

    interface IMyOtherTest
    {
        void MyTestMethod(int notOptional, int optional = 7);
    }

    class MyTest : IMyTest, IMyOtherTest
    {
        public void MyTestMethod(int notOptional, int optional = 0)
        {
            Console.WriteLine(string.Format("{0} {1}", notOptional, optional));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyTest myTest1 = new MyTest();
            myTest1.MyTestMethod(1);

            IMyTest myTest2 = myTest1;
            myTest2.MyTestMethod(2);

            IMyOtherTest myTest3 = myTest1;
            myTest3.MyTestMethod(3);
        }
    }
}

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

// Optput:
// 2 5
namespace ScrapCSConsole
{
    using System;

    interface IMyTest
    {
        void MyTestMethod(int notOptional, int optional = 5);
    }

    class MyTest : IMyTest
    {
        public void MyTestMethod(int notOptional, int optional)
        {
            Console.WriteLine(string.Format("{0} {1}", notOptional, optional));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyTest myTest1 = new MyTest();
            // The following line won't compile as it does not pass a required
            // parameter.
            //myTest1.MyTestMethod(1);

            IMyTest myTest2 = myTest1;
            myTest2.MyTestMethod(2);
        }
    }
}

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

// Optput:
// 2 5
namespace ScrapCSConsole
{
    using System;

    interface IMyTest
    {
        void MyTestMethod(int notOptional, int optional = 5);
    }

    class MyTest : IMyTest
    {
        void IMyTest.MyTestMethod(int notOptional, int optional = 9)
        {
            Console.WriteLine(string.Format("{0} {1}", notOptional, optional));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyTest myTest1 = new MyTest();
            // The following line won't compile as MyTest method is not available
            // without first casting to IMyTest
            //myTest1.MyTestMethod(1);

            IMyTest myTest2 = new MyTest();            
            myTest2.MyTestMethod(2);
        }
    }
}

Эрик Липперт написал интересную серию статей именно на эту тему: ="noreferrer">Крайние случаи необязательных аргументов

person Martin Brown    schedule 19.12.2013
comment
Значения по умолчанию компилируются в call-site, поэтому используются только значения того типа, для которого вы вызываете метод. - person keuleJ; 06.05.2021

Вам не нужно делать параметр необязательным в реализации. Тогда ваш код будет иметь больше смысла:

 public interface IFoo
 {
      void Bar(int i, int j = 0);
 }

 public class Foo
 {
      void Bar(int i, int j) { // do stuff }
 }

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

person Tor Haugen    schedule 31.07.2014
comment
Значение по умолчанию в реализации будет иметь эффект, если ваша ссылка набирается с классом, а не с интерфейсом. - person Martin Brown; 02.03.2017

Как насчет чего-то подобного?

public interface IFoo
{
    void Bar(int i, int j);
}

public static class IFooExtensions 
{
    public static void Baz(this IFoo foo, int i, int j = 0) 
    {
        foo.Bar(i, j);
    }
}

public class Foo
{
    void Bar(int i, int j) { /* do stuff */ }
}
person Dave    schedule 19.04.2012
comment
@Iznogood - одобренное вами здесь редактирование явно не является допустимым: stackoverflow.com/review/suggested-edits/1041521. Пожалуйста, будьте внимательнее при просмотре правок. - person LittleBobbyTables - Au Revoir; 19.11.2012

Следует учитывать, что происходит, когда используются фреймворки Mocking, которые работают на основе отражения интерфейса. Если в интерфейсе определены необязательные параметры, значение по умолчанию будет передано на основе того, что объявлено в интерфейсе. Одна из проблем заключается в том, что ничто не мешает вам установить различные необязательные значения в определении.

person Hitesh    schedule 14.11.2014