为什么接口上定义的C#4可选参数在实现类时没有强制执行?

时间:2011-02-07 14:59:10

标签: c# .net c#-4.0 interface optional-parameters

我注意到,如果在接口上将参数指定为可选参数,则使用C#4中的可选参数,您不必在任何实现类上使该参数可选:

public interface MyInterface
{
    void TestMethod(bool flag = false);
}

public class MyClass : MyInterface
{
    public void TestMethod(bool flag)
    {
        Console.WriteLine(flag);
    }
}

因此:

var obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

有谁知道为什么可选参数设计为以这种方式工作?

一方面,我认为覆盖接口上指定的任何默认值的能力是有用的,但老实说我不确定你是否应该能够在接口上指定默认值,因为这应该是一个实现决策

另一方面,这种断开意味着您不能总是交替使用具体类和接口。当然,如果在实现上指定了默认值,那么这不是问题,但是如果你将具体类作为接口公开(使用一些IOC框架来注入具体的类),那么真的没有具有默认值的点,因为调用者无论如何都必须始终提供它。

7 个答案:

答案 0 :(得分:224)

更新:This question was the subject of my blog on May 12th 2011. Thanks for the great question!

假设您有一个描述的接口,以及一百个实现它的类。然后你决定使其中一个接口的方法的参数之一可选。您是否建议正确的做法是让编译器强制开发人员找到该接口方法的每个实现,并使参数也可选?

假设我们这样做了。现在假设开发人员没有实现的源代码:


// in metadata:
public class B 
{ 
    public void TestMethod(bool b) {}
}

// in source code
interface MyInterface 
{ 
    void TestMethod(bool b = false); 
}
class D : B, MyInterface {}
// Legal because D's base class has a public method 
// that implements the interface method

D的作者应该如何使这项工作?您是否需要在您的世界中打电话给B的作者并要求他们向他们发送新版本的B,使该方法具有可选参数?

那不会飞。如果两个人调用B的作者,并且其中一个人想要默认为真并且其中一个人想要它是假的,该怎么办?如果B的作者拒绝参与该怎么办?

也许在这种情况下,他们会被要求说:

class D : B, MyInterface 
{
    public new void TestMethod(bool b = false)
    {
        base.TestMethod(b);
    }
}

建议的功能似乎给程序员带来了很多不便,代表功率没有相应的增加。这个功能的显着优点是什么证明了用户成本的增加?

答案 1 :(得分:46)

可选参数仅使用属性标记。此属性告诉编译器在调用站点插入该参数的默认值。

当C#代码编译为IL而不是JIT时,调用obj2.TestMethod();obj2.TestMethod(false);替换。

因此,在某种程度上,调用者始终使用可选参数提供默认值。这也会对二进制版本控制产生影响:如果更改默认值但不重新编译调用代码,它将继续使用旧的默认值。

  

另一方面,这种断开意味着您不能总是交替使用具体类和接口。

如果接口方法为implemented explicitly,则您已无法执行此操作。

答案 2 :(得分:26)

因为默认参数是在编译时解析的,而不是运行时。 因此,默认值不属于被调用的对象,而是属于通过它调用的引用类型。

答案 3 :(得分:5)

可选参数有点像我理解的宏替换。从方法的角度来看,它们并不是真正的可选项。如果您转换为接口,那么您所看到的行为就是您获得不同结果的行为。

答案 4 :(得分:2)

只是想在这里添加我的看法,因为其他答案确实提供了合理的解释,但不是完全让我满意的解释。

可选参数是在调用站点编译时注入默认值的语法糖。这与接口/实现没有任何关系,它可以被视为纯粹是带有可选参数的方法的副作用。所以,当你调用这个方法时,

public void TestMethod(bool value = false) { /*...*/ }

SomeClass.TestMethod(),实际上是SomeClass.TestMethod(false)。如果在接口上调用此方法,则从静态类型检查中,方法签名具有可选参数。如果您在没有可选参数的派生类实例上调用此方法,从静态类型检查来看,方法签名没有可选参数,并且必须使用完整参数调用。

由于可选参数的实现方式,这是自然的设计结果。

答案 5 :(得分:0)

var obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

使用

MyInterface obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

两者都导致错误

答案 6 :(得分:0)

感谢您的解释@eric-lippert

这是一些代码示例:

[Fact]
public void TestOptionalMethodArgument()
{
    var implementation = new TestHello();
    IHello @interface = implementation;

    Assert.Equal(23, @interface.Action());
    Assert.Equal(40, implementation.Action());
}

public class TestHello : IHello
{
    public int Action(int number = 40)
        => number;
}

public interface IHello
{
    int Action(int number = 23);
}