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

我注意到,对于c# 4中的可选参数,如果你在接口上指定了一个可选参数,那么< em >不< / em >必须在任何实现类上使该参数为可选:

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框架注入具体类),那么使用默认值就没有意义了,因为调用者无论如何都必须提供它。

131829 次浏览

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

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

当c#代码被编译为IL时,调用obj2.TestMethod();obj2.TestMethod(false);取代,而不是在jit时。

所以在某种程度上,它总是调用者提供默认值和可选参数。这对二进制版本控制也有影响:如果您更改了默认值,但不重新编译调用代码,它将继续使用旧的默认值。

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

如果接口方法是实现显式地,你已经不能这样做了。

从我的理解来看,可选参数有点像宏替换。从方法的角度来看,它们实际上不是可选的。它的一个产物是您看到的行为,如果您强制转换到一个接口,您将得到不同的结果。

更新:这个问题是我在2011年5月12日的博客的主题。谢谢你的好问题!

假设您有一个如您所描述的接口,以及100个实现它的类。然后决定将接口的一个方法的一个参数设置为可选。您是否建议编译器强制开发人员找到该接口方法的每个实现,并使参数也是可选的?

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


// 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);
}
}

提议的功能似乎给程序员增加了很多不便,而代表能力却没有相应的增加。这个功能有什么引人注目的好处,可以证明用户增加的成本是合理的?


更新:在下面的评论中,supercat提出了一个语言特性,它将真正地为语言增加功能,并支持一些类似于这个问题中描述的场景。仅供参考,这个特性——接口中方法的默认实现——将被添加到c# 8中。

我想在这里补充一下我的观点,因为其他答案确实提供了合理的解释,但并不是完全让我满意的。

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

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

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

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

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

结果都是假的

谢谢你的解释@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);
}