避免重复使用界面的默认设置

11

我有一个带有默认参数的接口,我想从实现类中调用实现方法(除了从外部调用)。 我还想使用其默认参数。

但是,如果我只是按名称调用该方法,我无法使用默认参数,因为它们仅在接口中定义。我可以在实现方法中重复默认规范,但由于DRY和所有详细信息(特别是编译器不会检查它们是否与接口的默认值匹配!),这不太可能。

我通过引入名为_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();

        // ...
    }
}

这种模式有什么问题或更好的解决方法吗?(顺便说一下,我认为这是语言的一个缺陷,我必须做类似这样的事情)
编辑:不是Why are C# 4 optional parameters defined on interface not enforced on implementing class?的重复... 我主要是在问如何绕过这个语言怪癖,而不是询问为什么它被设计成这样。

相关:https://dev59.com/xHE85IYBdhLWcg3w3Xr6 - Arturo Menchaca
您不需要重复默认值,在我的示例中,您可以注意到显式实现正在获取接口参数,在该显式实现中,我甚至没有默认值。 - bto.rdz
这并不会让问题变得不那么有趣,但是在接口上放置参数值让我感到非常困扰。对我来说,该值取决于实现,因此不应由接口强制执行。我认为重载会更加合适。尽管如此,这仍然是一个有趣的边角案例。 - Kevin Gosse
@KooKiz,我有点同意你的看法,但是你提出的问题可以通过在接口中将所有可选参数指定为默认值null(对于值类型使用Nullable<T>)来解决。然后,每个实现都可以使用??来默认到实际默认值...例如,在接口中int? direction = null,实现可以从direction = direction ?? 90开始,以默认为90。 - JoelFan
4个回答

5
你有三个选择。

使用扩展方法作为默认选项

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在你的问题中评论中链接到的问题和最佳答案:为什么在接口上定义的C# 4可选参数在实现类上不受强制执行?

不需要显式地实现接口... 即使我不重复默认值,编译器也会识别它。 - JoelFan
@JoelFan 我知道这一点。重要的是,如果您明确实现了接口,则接口默认值和实现默认值不可能失去同步,因为没有实现指定的默认值。 - Timothy Shields
我想说的是,即使没有显式地实现接口,我也可以省略实现中的默认值... 我不明白显式实现能给我带来什么好处。 - JoelFan
太多的选项,但明确的实现肯定是正确的方式。 - bto.rdz

3

当您将其转换并作为接口调用时,使用显式实现,您将获得接口默认值,当您使用类调用它时,您将获得类默认值,例如:

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
从这个类的默认值中获取
20,10


我可以在任何地方使用*((Imovable)this)*,即使没有明确实现接口,但这正是我试图避免的。 - JoelFan
@JoelFan 但是IMovable是一个接口,它没有内容或定义,也没有意义,你需要定义这个接口,接口只表示某个对象包含某些方法或属性,而不再多余。 - bto.rdz
@JoelFan IMovable 只是在说,嘿,你需要定义这个方法,而这就是你正在做的事情,它是一个接口的定义,我不明白为什么在这一点上是错误的。 - bto.rdz

1
你可以将其显式转换为接口。
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

1
这就是我使用 _this 的目的,试图避免这种情况。 - JoelFan
1
如果是这种情况,为什么不将接口转换为抽象类,并使用没有参数的Move方法实现,该方法调用您的默认参数90和100的移动方法。 - Paulo Prestes
此外,我认为这不是语言上的缺陷,因为默认参数在编译时解析,所以编译器只能确定您是否要使用接口类型来使用默认参数。 - Paulo Prestes
1
我喜欢抽象类的概念,但这会阻止我从更有用的基类派生。 - JoelFan

-1

因为您实现的方法与接口不完全相同,所以编译器不知道您想要实现哪个方法。

以下是您的答案。

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's declaration
    public void Move(int direction = 90, int speed = 100)
    {
        // ...
    }

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

不是这样的!编译器确实将其视为实现(而不会重复默认值)!证据是我可以通过接口调用它。无论我省略默认值、重复它们甚至改变它们(!!!),C#都将其识别为接口的实现。 - JoelFan
哦!谢谢。顺便说一下,如果我不显式地传递参数,在Visual Studio中会出现错误。 - Pajace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接