Избегайте повторения значений по умолчанию интерфейса

Иногда у меня есть интерфейс с параметрами по умолчанию, и я хочу вызвать метод реализации из внутри класса реализации (в дополнение к нему извне). Я также хочу использовать его параметры по умолчанию.

Однако, если я просто вызываю метод по имени, я не могу использовать параметры по умолчанию, потому что они определены только в интерфейсе. Я мог бы повторить спецификации по умолчанию в методе реализации, но это нежелательно из-за СУХОЙ и все, что влечет за собой (особенно тот факт, что компилятор не проверял, что они действительно соответствуют настройкам по умолчанию!)

Я решаю это, введя член с именем _this, который совпадает с this, за исключением того, что он объявлен как тип интерфейса. Затем, когда я хочу использовать параметры по умолчанию, я вызываю метод с _this. Вот пример кода:

public interface IMovable
{
    // I define the default parameters in only one place
    void Move(int direction = 90, int speed = 100);
}

public class Ball: IMovable
{
    // Here is my pattern
    private readonly IMovable _this;

    public Ball()
    {
        // Here is my pattern
        _this = this;
    }

    // I don't want to repeat the defaults from the interface here, e.g.
    // public void Move(int direction = 90, int speed = 100)
    public void Move(int direction, int speed)
    {
        // ...
    }

    public void Play()
    {
        // ...

        //This would not compile
        //Move();

        // Now I can call "Move" using its defaults
        _this.Move();

        // ...
    }
}

Есть ли что-то не так с этим шаблоном или способом решить проблему лучше? (Кстати, я думаю, что это недостаток в языке, который я должен сделать примерно так)

РЕДАКТИРОВАТЬ: Не дублировать Почему дополнительные параметры С# 4, определенные на интерфейсе, не выполняются при реализации класса?... Я прежде всего спрашивая, как обойти эту языковую причуду, а не спрашивать, почему она была разработана именно так.

Ответ 1

У вас есть три варианта.

Использовать метод расширения по умолчанию

public interface IMovable
{
    void Move(int direction, int speed);
}

public static MovableExtensions
{
    public static void Move(this IMovable movable)
    {
        movable.Move(90, 100);
    }
}

Явно реализовать интерфейс

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

public class Ball : IMovable
{    
    void IMovable.Move(int direction, int speed)
    {
    }
}

Повторить аргументы по умолчанию

public class Ball : IMovable
{    
    public void Move(int direction = 90, int speed = 100)
    {
    }
}

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

Я признаю, что это объяснение не очень удовлетворительно. Если вам нужна дополнительная информация о том, почему язык был разработан таким образом, прочитайте вопрос и ответьте на вопрос Giorgi Nakeuri в своем комментарии к вашему вопросу: Почему существуют дополнительные параметры С# 4, определенные на интерфейсе не выполняются при реализации класса?

Ответ 2

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

public class Ball : IMovable
{
    //this uses the interface defaults
    //notice how you dont need to define the default values again
    //they are only specified once, in the interface definition
    void IMovable.Move(int direction, int speed)
    {
        Debug.WriteLine(direction + "," + speed);
    }

    //now for the specific case of this class you can have your own defaults
    //or none, just what ever fits your needs
    public void Move(int direction = 20, int speed = 10)
    {
        Debug.WriteLine(direction + ","+ speed);
    }

    public void Play()
    {
        Debug.WriteLine("From interface");
        ((IMovable) this).Move();
        Debug.WriteLine("From this class defaults");
        Move();
    }
}

И вывод

Из интерфейса
  90, 100
  Из этого класса defaults
  20, 10

Ответ 3

Вы можете явно указать на интерфейс.

using System;
using System.IO;
using System.Threading.Tasks;

public class Test {
    public static void Main()
    {
        var t = new Test1();
        t.Play();
    }

}
public interface IMovable
{
    // I define the default parameters in only one place
    void Move(int direction = 90, int speed = 100);
}

public class Test1 : IMovable{
    public void Move (int direction, int speed)
    {
        Console.Write($"{direction} {speed}");
    }

    public void Play (){
        ((IMovable)this).Move();
    }
}

Вывод:

90 100

Или вы можете преобразовать свой интерфейс в абстрактный класс.

using System;
using System.IO;
using System.Threading.Tasks;

public class Test {
    public static void Main()
    {
        var t = new Test1();
        t.Play();
    }

}
public abstract class  IMovable
{
    // I define the default parameters in only one place
    public abstract void Move(int direction, int speed);

    public void Move(){
        this.Move(90, 100);
    }
}

public class Test1 : IMovable{

    public virtual void Move(int direction, int speed){

        Console.Write($"{direction} {speed}");
    }

    public void Play (){
        this.Move();
    }
}

Вывод:

90 100

Ответ 4

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

Вот ваш ответ.

public interface IMovable
{
    void Move(int direction = 90, int speed = 100);
}

public class Ball : IMovable
{
    // the method you want to implement from interface 
    // MUST same with interface declaration
    public void Move(int direction = 90, int speed = 100)
    {
        // ...
    }

    public void Play()
    {
        Move();
    }
}