假设我们有:
public class FooBase
{
public void Write(byte value)
{
//something
}
public void Write(int value)
{
//something
}
}
public class Foo : FooBase
{
public void Write(decimal value)
{
//something
}
}
比这个:
var writer = new Foo();
writer.Write(5); //calls Write(decimal) !!
writer.Write((byte)6); //calls Write(decimal) !!
将调用Write(十进制)重载。为什么?我怎样才能调用Write(int)或Write(byte)?
答案 0 :(得分:16)
是的,它会那样做。这实际上是我的brainteaser #1。在通常使用的意义上,这不是真正的类型推断 - 它是重载决策。这就是你需要查看规范的地方。
现在,Writer
的编译时类型为Foo
。
当你调用writer.Write
时,编译器将从类型Foo
开始并向上运行类型为hiearchy,直到它找到一个最初在该类型中声明的方法,它可以合法地使用参数调用已经给了。一旦找到它,它就不会再进一步升级。
现在,5可以转换为decimal
(因此在专门转换为byte
后为5) - 所以Foo.Write(decimal)
是适用的函数成员为你的方法调用 - 这是被调用的。它甚至不考虑FooBase.Write
重载,因为它已经找到匹配。
到目前为止,这是合理的 - 想法是向基类型添加方法不应该更改子类型不知道的现有代码的重载决策。当涉及到覆盖时,这会有所下降。让我们稍微更改您的代码 - 我将删除byte
版本,将Write(int)
设为虚拟并在Foo
中覆盖它:
public class FooBase
{
public virtual void Write(int value)
{
//something
}
}
public class Foo : FooBase
{
public override void Write(int value)
{
//something
}
public void Write(decimal value)
{
//something
}
}
现在new Foo().Write(5)
会做什么?它仍然调用Foo.Write(decimal)
- 因为Foo.Write(int)
在Foo中没有声明,只有重写。如果您将override
更改为new
,则会调用它,因为这会被视为全新的方法声明。
我认为这方面是违反直觉的 - 并且版本控制不需要它,就像你在子类中重写一个方法一样,你清楚地知道它在基类中。
故事的寓意:尽量不要这样做。你最终会让人困惑。如果您派生自某个类,请不要添加具有相同名称但具有不同签名的新方法,如果您可以提供帮助的话。
答案 1 :(得分:7)
您可以像这样致电Write(byte)
:
((FooBase)writer).Write((byte)6);
通常,C#更喜欢直接使用类型上定义的重载并转换参数,而不是使用在父类型上定义的重载。
以这种方式使用方法遮蔽基类方法是一种糟糕的风格。
答案 2 :(得分:6)
这是一个经常被问到的问题。乔恩的分析当然是正确的。如需进一步阅读,请参阅我的文章:
http://blogs.msdn.com/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx