多态性
多态性
-
在运行时,在方法参数和集合或数组等位置,派生类的对象可以作为基类的对象处理。 发生此情况时,该对象的声明类型不再与运行时类型相同。
基类可以定义并实现虚方法,派生类可以重写这些方法,即派生类提供自己的定义和实现。
虚成员
当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。 派生类的设计器可以选择是否
-
重写基类中的虚拟成员。
-
继承最接近的基类方法而不重写它
-
定义隐藏基类实现的成员的新非虚实现
仅当基类成员声明为 virtual 或 abstract 时,派生类才能重写基类成员。 派生成员必须使用 override 关键字显式指示该方法将参与虚调用。 以下代码提供了一个示例:
public class BaseClass { public virtual void DoWork() { } public virtual int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public override void DoWork() { } public override int WorkProperty { get { return 0; } } }
字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。 当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。 以下代码提供了一个示例:
DerivedClass B = new DerivedClass(); B.DoWork(); // Calls the new method. BaseClass A = (BaseClass)B; A.DoWork(); // Also calls the new method.
虚方法和属性允许派生类扩展基类,而无需使用方法的基类实现。 有关更多信息,请参见 使用 Override 和 New 关键字进行版本控制(C# 编程指南)。 接口提供另一种方式来定义将实现留给派生类的方法或方法集。 有关更多信息,请参见 接口(C# 编程指南)。
使用新成员隐藏基类成员
如果希望派生成员具有与基类中的成员相同的名称,但又不希望派生成员参与虚调用,则可以使用 new 关键字。 new 关键字放置在要替换的类成员的返回类型之前。 以下代码提供了一个示例:
public class BaseClass { public void DoWork() { WorkField++; } public int WorkField; public int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public new void DoWork() { WorkField++; } public new int WorkField; public new int WorkProperty { get { return 0; } } }
通过将派生类的实例强制转换为基类的实例,仍然可以从客户端代码访问隐藏的基类成员。 例如:
DerivedClass B = new DerivedClass(); B.DoWork(); // Calls the new method. BaseClass A = (BaseClass)B; A.DoWork(); // Calls the old method.
阻止派生类重写虚拟成员
无论在虚拟成员和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员永远都是虚拟的。 如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则类 C 继承该虚拟成员,并且可以选择重写它,而不管类 B 是否为该成员声明了重写。 以下代码提供了一个示例:
派生类可以通过将重写声明为 sealed 来停止虚拟继承。 这需要在类成员声明中的 override 关键字前面放置 sealed 关键字。 以下代码提供了一个示例:
public class C : B { public sealed override void DoWork() { } }
在上一个示例中,方法 DoWork 对从 C 派生的任何类都不再是虚拟方法。 即使它们转换为类型 B 或类型 A,它对于 C 的实例仍然是虚拟的。 通过使用 new 关键字,密封的方法可以由派生类替换,如下面的示例所示:
public class D : C { public new void DoWork() { } }
在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWork。 如果使用类型为 C、B 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的 DoWork 实现。
从派生类访问基类虚拟成员
已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。 以下代码提供了一个示例:
public class Base
{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}