Why are C# 4 optional parameters defined on interface not enforced on implementing class?
我注意到,对于C 4中的可选参数,如果在接口上指定参数为可选参数,则不必在任何实现类上使该参数成为可选参数:
1 2 3 4 5 6 7 8 9 10 11 12
| public interface MyInterface
{
void TestMethod(bool flag = false);
}
public class MyClass : MyInterface
{
public void TestMethod(bool flag)
{
Console.WriteLine(flag);
}
} |
因此:
1 2 3 4 5
| var obj = new MyClass ();
obj .TestMethod(); // compiler error
var obj2 = new MyClass () as MyInterface ;
obj2 .TestMethod(); // prints false |
有人知道为什么可选参数是这样设计的吗?
一方面,我认为重写接口上指定的任何默认值的能力是有用的,但老实说,我不确定您是否应该甚至能够在接口上指定默认值,因为这应该是一个实现决策。
另一方面,这种断开意味着您不能总是交替地使用具体的类和接口。当然,如果在实现中指定了默认值,这不会是一个问题,但是如果您将具体的类公开为接口(例如,使用一些IOC框架来注入具体的类),那么使用默认值是没有意义的,因为调用者无论如何都必须提供它。
- 因为它们是可选的?
- 但可以将对象实例强制转换为MyInterface,并使用可选参数:((MyInterface)obj).TestMethod();调用它。
- @但是如果您说这个参数在合同中是可选的,为什么您不允许实现者使它成为可选的呢?这难道不会让任何想使用合同的人感到困惑吗?
- 我认为在这种情况下,可以说参数在实现时是可选的,而不是在调用实现方法时。当你在类中调用方法时,你必须遵循类规则(参数在类中不是可选的,所以没有它你不能调用方法),而在实现接口时,你必须遵循接口规则,这样您可以使用/不使用可选参数重写方法。只是一个意见。
- 更多详细的问题解释请点击这里->geekswithblogs.net/blackrabbitcoder/archive/2010/06/17/…
更新:这个问题是我2011年5月12日博客的主题。谢谢你的提问!
假设您有一个如您所描述的接口,以及100个实现它的类。然后,您决定将接口方法之一的参数设置为可选参数。您是否建议正确的做法是让编译器强制开发人员查找该接口方法的每个实现,并使参数也是可选的?
假设我们这样做了。现在假设开发人员没有实现的源代码:
1 2 3 4 5
| // in metadata:
public class B
{
public void TestMethod(bool b) {}
} |
1 2 3 4 5 6 7 8
| // 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的作者只是拒绝合作呢?
在这种情况下,他们可能需要说:
1 2 3 4 5 6 7
| class D : B, MyInterface
{
public new void TestMethod (bool b = false)
{
base.TestMethod(b );
}
} |
所提出的特性似乎给程序员增加了许多不便,而没有相应增加代表性的能力。这项功能的显著优点是什么?它为用户增加了成本?
- 既然您这样说了,那么对实现类进行限制以使用与接口相同的默认值是没有意义的。
- 我从来没想过它会造成多大的痛苦。我想唯一让人觉得接口方法在任何实现中都有相同的可选参数的方法是在处理重载的接口中有扩展方法
- 将来的.NET是否有可能允许接口定义指定"默认"实现?这在概念上似乎很简单:用一些特殊的元数据创建一个包含方法的静态泛型类,当为实现接口的类构建接口vtable时,用相应的"默认方法"类中的相应点填充任何缺失的点。当类方法按值传递"this"时,结构方法通过引用传递"this"的事实可以通过拥有一个用于结构的静态类和一个用于类的静态类来处理。
- @Supercat:我们没有任何这样的计划,但是这样的功能是可能的。以前有人提议过。在C 4的设计过程中,我们讨论了许多我们称之为"扩展一切"的特性——我们有扩展方法,那么拥有扩展事件、扩展属性、扩展构造函数、扩展接口等意味着什么。可以"附加"到接口并说"这些方法是该接口的默认实现"的类是描述"扩展接口"的一种可能方法。一个有趣的想法。
- 扩展方法只是编译器的糖分;让clr识别"默认接口方法"的一个优点是,它允许成员添加到接口中而不破坏现有的实现。例如,可以向IList添加一个排序方法,并让它默认调用一个排序实现,该实现可以按索引访问和交换元素,但让List使用自己的排序方法实现IList.Sort。我不确定需要什么来避免意外地使用一个最初不是的成员实现一个新创建的接口成员…
- …接口实现。我喜欢vb.net的一点是,很清楚哪些成员是接口实现,哪些成员不是。
- 实际上,更好的用法是添加协变的IReadableList接口,默认情况下让IList实现通过调用索引读写属性的"get"一半来实现索引只读属性。
- @Ericlippert:您是说编译器团队所采取的观点是,将现有方法参数更改为可选参数不足以导致接口定义发生中断性更改?有道理。
- 在我看来仍然是违反直觉的。我认为如果接口有一个可选参数,它将需要实现类具有相同的可选参数。由于可选参数主要与提供带该参数的版本和不带该参数的版本相同,因此实现类可以选择实现两个显式版本。如果希望现有接口中的某个参数成为可选的,则创建一个不带该参数的附加版本。该类可以再次选择1(可选)或2(单独)。我觉得更直观。
- 为了阐明我为什么认为这是不直观的原因:对我来说,一个可选参数是"方法调用方的可选参数",而不是"接口实现方的可选参数"。
- "在你的世界里,他们是否需要打电话给B的作者,让他们给他们寄一份新的版本[…]?"不,不需要打电话。B不应该再被认为是MyInterface的一个实现,而D的作者可以被编译器意识到这一点。D的作者需要用一个接受默认参数的testmethod来实现D,因为这正是接口作者所要求的——您反对允许接口作者强制一个约束,因为可能有人想破坏它。这不是一个好论点。
- @CPTPHIL:我的答案后半部分讨论了你的建议。正如我注意到的,它增加了不便,也没有添加任何相应的电源。不过,更一般的情况是:接口描述了哪些特性是可用的,而不是实现者如何公开这些特性。C为实现者提供了选择如何实现接口契约的广泛框架。这是通过设计实现的;接口是实现者的仆人,而不是它的主人。
- @Ericlippert在为方便编码而指定可选参数的情况下,是的,在实现中强制实施不便是违反直觉的。但是,在使用可选参数来最小化方法过载的情况下,这是一个强大的特性,这些可选值可能具有很大的意义,忽略它们肯定会削弱这种能力。更不用说具体实现指定的默认值与接口不同。这似乎是一个非常奇怪的断开允许。
- 我同意@philofinitejest。一个接口告诉我们什么事情可以做。如果传递给我的接口实现的默认值与接口指定的值不同,我如何知道这一点?嘿,我有一个将true作为默认值传递的接口,那么为什么这个东西会变为false呢?看起来我没有得到我所期望的界面。更糟糕的是,我现在必须编程到一个实现,而不是一个接口。
- @Aaronburro:当然,每个设计决策都有利弊;如果没有利弊,那么决策就很容易了。C设计团队多年来一直拒绝"可选参数"功能,因为这类问题,但该功能受到了用户的强烈要求,在处理为历史上具有此功能(vb)的语言设计的遗留对象模型时非常有用,因此设计团队决定在完全重新设计的情况下使用它。你和菲尔提出的合理的反对意见。这些也不是最大的反对意见!有许多艰难的决定。
- @Ericlippert,我刚刚遇到的一个问题(这使我了解了这篇文章),就是实现类也可能选择提供不同的默认值。实际上,我希望编译器不仅需要相同的默认参数,还需要相同的默认值。如果MyClass将方法定义为public void TestMethod(bool flag=true){...}的话,很容易遇到意外的行为。
- 真正令人困惑的是IMine mine = new Mine(); mine.Explode();,其中IMine定义Explode(bool destroyNearbyMines = false),Mine定义Explode(bool destroyNearbyMines = true)。哪一个先例?接口的默认值还是实现?我认为接口中的默认值不应该被实现侵犯。不过,我觉得这模棱两可。(ideone示例)
- @crush:c使用编译时类型来解决编译时的过载解决问题,因此在您的示例中,它将使用接口。
- @Ericlippert抱歉,这是一个反问性的问题,我想我的观点是,对于开发者来说,它可能变得模糊不清,他们指定的默认值是什么。更准确地说,它可能变得模糊不清,它们应该指定哪个默认值。任何智能化的IDE都会告诉他们,如果引用的是IMine类型,那么缺省值就是false。但是,根据Mine的实现,它不会告诉他们默认值应该是什么(因为它不知道使用的是哪个实现)。这就是默认值存在问题的原因。
- @压碎:你不会从我这里得到默认值会产生很多奇怪的情况的论据;我在我的博客上写了很多关于这个的文章。在过去的十年里,由于这些奇怪的角落案例,C团队一直拒绝将它们添加到语言中,但是来自用户的请求——特别是那些希望使用为Visual Basic设计的遗留对象模型的用户——既多又引人注目。
- 我刚刚读完你的博客文章(不知何故我第一次读到答案时就错过了)。好东西。出于这个原因,我拒绝在我创建的任何接口中放置默认值。如果有人想要使用默认值,那么他们需要一个实现引用类型。
- 对此答案的注释:那么接口中可选参数的用途是什么?如果你试图把可选的东西放到接口中,这难道不是一个编译器错误吗?
- @山姆:没有接口上的特性将击败特性的全部要点:减轻必须使用COM API的C可调用版本的开发人员的负担,在这些版本中,方法有几十个可选参数。这些东西都用C表示为接口。这些场景是将该功能添加到C的99%原因;当我加入VSTO团队时,我强烈游说C团队出于这个原因添加该功能。
- @Ericlippert我在使用Office Interop时遇到了同样的问题。很高兴你指出。谢谢您。
- @supercat:re:2012年关于默认接口实现的评论。有传言说这是为C 8考虑的。我在Java中使用了默认的接口实现,它是我认为Java比C语言做得更好的几个领域之一。如果这个功能可以进入到C 8,那就太好了。
- @Ericlippert:这个特性是为了让编译器在默认实现中烘焙,还是打算让框架自动为没有它们的类创建实现。我并不认为前者有那么多价值,而且我担心如果.NET的维护人员着手实现它,它可能会与后者发生冲突。我想看到的是接口IFoo可以通过它指定任何实现IBar的类也应被视为IFoo的实现。
- @Ericlippert:我认为可以在不破坏二进制兼容性的情况下,通过说当试图使用一个类型(例如thisStruct作为不受支持的接口类型)时,它将搜索该类型的属性以查找标记为自动扩展的任何接口,并为新接口中添加的每个成员自动生成一个impl。像double newInterface foo(int param1) { return newInterface.oldInterface.structWrap_foo(ref thisStruc it, param1); }这样的做法。有语言支持也不错,但是…
- …例如,让函数接受IExtendedEnumerable类型的参数,但能够接收对不包含该接口实现但确实实现IEnumerable的类类型的引用的能力,似乎比编译器可能尝试添加的任何语法结构都更有价值。
- @Supercat:我不知道具体细节是什么;我不再关注C队的日常活动了,很遗憾。尝试查看Github论坛。
一个可选参数刚刚用一个属性标记。这个属性告诉编译器在调用站点插入该参数的默认值。
当C代码编译到IL时,调用obj2.TestMethod();被obj2.TestMethod(false);替换,而不是在JIT时。
所以在某种程度上,它总是调用方提供带有可选参数的默认值。这对二进制版本控制也有影响:如果更改默认值但不重新编译调用代码,它将继续使用旧的默认值。
On the other hand, this disconnect means you can't always use the concrete class and the interface interchangeably.
如果接口方法是显式实现的,则您已经不能这样做。
因为默认参数是在编译时解析的,而不是运行时。因此,默认值不属于被调用的对象,而是属于被调用的引用类型。
根据我的理解,可选参数有点像宏替换。从方法的角度来看,它们实际上不是可选的。其中的一个伪影就是当你投射到一个接口上时你看到的不同结果的行为。