我注意到使用 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 框架注入具体类),那么真的没有点具有默认值,因为调用者无论如何都必须始终提供它。
MyInterface
并使用可选参数调用它:((MyInterface)obj).TestMethod();
。
更新: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);
}
}
提议的特性似乎给程序员增加了很多不便,但代表权并没有相应增加。这个特性有什么引人注目的好处,证明增加的用户成本是合理的?
更新:在下面的评论中,supercat 建议了一种语言功能,该功能可以真正增加语言的功能,并启用一些类似于此问题中描述的场景。仅供参考,该功能(接口中方法的默认实现)将添加到 C# 8。
可选参数只是用属性标记。该属性告诉编译器在调用点插入该参数的默认值。
当 C# 代码编译为 IL 时,调用 obj2.TestMethod();
将替换为 obj2.TestMethod(false);
,而不是在 JIT 时间。
所以在某种程度上它总是调用者提供带有可选参数的默认值。这也对二进制版本控制产生影响:如果您更改默认值但不重新编译调用代码,它将继续使用旧的默认值。
另一方面,这种断开连接意味着您不能总是交替使用具体类和接口。
如果接口方法是 implemented explicitly,您已经不能这样做了。
因为默认参数是在编译时解析的,而不是运行时解析的。所以默认值不属于被调用的对象,而是属于被调用的引用类型。
据我了解,可选参数有点像宏替换。从方法的角度来看,它们并不是真正可选的。其中的一个产物是,如果你转换到一个接口,你会看到你得到不同结果的行为。
只是想在这里添加我的看法,因为其他答案确实提供了合理的解释,但不是完全让我满意的解释。
可选参数是在调用站点编译时注入默认值的语法糖。这与接口/实现没有任何关系,它可以被视为纯粹是带有可选参数的方法的副作用。因此,当您调用该方法时,
public void TestMethod(bool value = false) { /*...*/ }
像 SomeClass.TestMethod()
,它实际上是 SomeClass.TestMethod(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);
}
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
并且都导致错误