Effective C# Item 20: Distinguish Between Implementing Interfaces and Overriding Virtual Functions
乍看之下,实现接口和重写虚方法是相似的,它们都为一个声明好的成员提供了定义。这种看法是错误的。实现接口和重写虚方法是有很大区别的。在默认情况下,在接口中声明的成员不是虚拟的。派生类不能够重写基类中实现的接口。
有一种方法可以让派生类修改基类中接口的实现。要实现这一点,我们需要为派生类创建一个钩子(hook)。
下面我们举一个例子,一个简单的接口和一个类对接口的实现如下:
{
void Message();
}
public class MyClass : IMsg
{
public void Message()
{
Console.WriteLine("MyClass");
}
}
Message()方法是MyClass类的公共接口中的一部分。我们也可以通过IMsg接口来访问MyClass类型中的成员。现在我门来让派生类中的情况稍微复杂一些:
{
public new void Message()
{
Console.WriteLine("MyDerivedClass");
}
}
注意到我们在这里使用了new关键字来定义之前已经存在的Message()方法。MyClass.Message()方法不是虚方法,派生类是不能对它进行重写的。这里派生类创建了一个新的Message(),而不是重写了基类中的方法。因此,我们还是可以通过IMsg接口来访问到基类中的Message()方法。
d.Message(); //结果为"MyDerivedClass"
IMsg m = d as IMsg;
m.Message(); //结果为"MyClass"
接口方法不是虚拟的,当我们实现接口时,就是为我们的类声明了一个有特殊含义的具体实现。
但是有时候我们希望创建接口,在基类中实现,并且在派生类中修改它们的行为。这是可以做到的。我们有两种选择,如果我们的基类是不可达的,那么我们可以在派生类中重新实现该接口:
{
public new void Message()
{
Console.WriteLine("MyDerivedClass");
}
}
额外添加的IMsg改变了我们派生类的行为。现在IMsg.Message()使用的将是派生类中实现的版本。
d.Message(); //结果为"MyDerivedClass"
IMsg m = d as IMsg;
m.Message(); //结果为"MyDerivedClass"
我们依然需要使用new关键字来声明MyDerivedClass.Message()方法。但是这仍然存在问题。通过基类的引用我们仍然会得到基类中的实现版本。
解决这个问题的唯一方法是修改基类,将这个接口方法声明为虚拟的。
{
public virtual void Message()
{
Console.WriteLine("MyClass");
}
}
这样所有从MyClass派生的类都可以声明它们自己的Message()了。不论是通过派生类,接口还是基类,调用的始终会是重写的版本。
如果我们不喜欢这种混乱的虚拟函数,可以稍微对基类作些修改:
{
public virtual void Message();
}
现在我们可以实现一个接口而不完全实现其中的方法。因为将方法声明为抽象的,我们必须在派生类中提供它的具体实现。IMsg是MyClass声明中的一部分,但是其中方法的具体定义却转移到派生类中实现。
实现接口比创建和重写虚方法有更多的选择。我们可以将实现声明为密封的,虚拟的,或者抽象的。我们可以根据具体情况来决定派生类是否可以修改基类中对接口的默认实现。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录