理解被覆盖的虚方法属于引入它的类,而不是覆盖它的类,这样就更容易理解类成员的绑定方式。除了使用dynamic 对象时,C# 中的所有绑定都在编译时解析。如果BaseClass 声明了一个虚拟方法foo,并且DerivedClass:BaseClass 覆盖foo,则尝试在BaseClass 类型的变量上调用foo 的代码将始终绑定到虚拟方法“slot”@987654328 @,这又将指向实际的DerivedClass.foo 方法。
这种理解在处理泛型时尤其重要,因为在 C# 中,与 C++ 不同,泛型类型的成员是根据泛型的约束来绑定的,而不是根据具体的泛型类型。例如,假设有一个SubDerivedClass:DerivedClass 创建了一个new virtual 方法foo(),并且定义了一个方法DoFoo<T>(T param) where T:BaseClass {param.foo();}。 param.foo() 调用将绑定到 BaseClass.foo,即使该方法被调用为 DoFoo<SubDrivedClass>(subDerivedInstance)。如果在调用foo 之前将参数强制转换为SubDerivedClass,则调用将绑定到SubDrivedClass.foo(),但编译器在生成DoFoo<T> 时无法判断T 将比BaseClass 更具体, 它不能绑定到BaseClass 中不存在的任何东西。
顺便说一句,如果一个类可以同时覆盖一个基类成员并创建一个新成员,那么有时它会很有用;例如,给定一个具有一些抽象只读属性的抽象基类ReadableFoo,如果一个类MutableFoo 既可以为该属性提供覆盖并定义一个具有相同名称的读写属性,那将很有帮助。不幸的是,.net 不允许这样做。鉴于这样的限制,最好的方法可能是ReadableFoo 提供一个具体的非虚拟只读属性,该属性调用具有不同名称的protected abstract 方法来获取值。这样一来,派生类就可以用读写属性(调用相同的虚拟方法进行读取,或调用新的(可能是虚拟的)写入方法)隐藏只读属性。
(以下未测试)
class BaseClass
{
public virtual void foo() {Console.WriteLine("BaseClass.Foo");
}
class DerivedClass:BaseClass
{
public override void foo() {Console.WriteLine("Derived.Foo");
}
class SubDerivedClass:DerivedClass
{
public new virtual void foo() {Console.WriteLine("SubDerived.Foo");
}
class MegaDerivedClass:SubDerivedClass
{
public override void foo() {Console.WriteLine("MegaDerived.Foo");
}
void DoFoo1<T>(T param) where T:BaseClass
{
param.foo();
}
void DoFoo1<T>(T param) where T:SubDerivedClass
{
param.foo();
}
void test(void)
{
var megaDerivedInstance = new MegaDerivedClass();
DoFoo1<MegaDerivedClass>(megaDerivedInstance);
DoFoo2<MegaDerivedClass>(megaDerivedInstance);
}
SubDerivedClass 有两个虚拟的 foo() 方法:BaseClass.foo() 和 SubDerivedClass.foo()。 MegaDerivedClass 具有相同的两种方法。请注意,从SubDerivedClass() 派生的类尝试覆盖foo 将覆盖SubDerivedClass.foo(),并且不会影响BaseClass.foo();通过上述声明,SubDerivedClass 的任何派生项都不能覆盖BaseClass.Foo。另请注意,将SubDerivedClass 或其子类的实例强制转换为DerivedClass 或BaseClass 将公开BaseClass.foo 用于调用的虚拟方法。
顺便说一句,如果SubDerivedClass 中的方法声明是friend new virtual void foo() {Console.WriteLine("SubDerived.Foo");,同一程序集中的其他类就不可能覆盖BaseClass.foo()(因为任何覆盖foo() 的尝试都会覆盖SubDerivedClass.foo()) ,但从 SubDerivedClass 派生的程序集外部的类不会看到 SubDerivedClass.foo(),因此可以覆盖 BaseClass.foo()。