多态性(C# 编程指南)Polymorphism (C# Programming Guide)

多态性常被视为自封装和继承之后,面向对象的编程的第三个支柱。Polymorphism is often referred to as the third pillar of object-oriented programming, after encapsulation and inheritance. Polymorphism(多态性)是一个希腊词,指“多种形态”,多态性具有两个截然不同的方面:Polymorphism is a Greek word that means "many-shaped" and it has two distinct aspects:

  • 在运行时,在方法参数和集合或数组等位置,派生类的对象可以作为基类的对象处理。At run time, objects of a derived class may be treated as objects of a base class in places such as method parameters and collections or arrays. 在出现此多形性时,该对象的声明类型不再与运行时类型相同。When this polymorphism occurs, the object's declared type is no longer identical to its run-time type.
  • 基类可以定义并实现方法,派生类可以重写这些方法,即派生类提供自己的定义和实现 。Base classes may define and implement virtual methods, and derived classes can override them, which means they provide their own definition and implementation. 在运行时,客户端代码调用该方法,CLR 查找对象的运行时类型,并调用虚方法的重写方法。At run-time, when client code calls the method, the CLR looks up the run-time type of the object, and invokes that override of the virtual method. 你可以在源代码中调用基类的方法,执行该方法的派生类版本。In your source code you can call a method on a base class, and cause a derived class's version of the method to be executed.

虚方法允许你以统一方式处理多组相关的对象。Virtual methods enable you to work with groups of related objects in a uniform way. 例如,假定你有一个绘图应用程序,允许用户在绘图图面上创建各种形状。For example, suppose you have a drawing application that enables a user to create various kinds of shapes on a drawing surface. 你在编译时不知道用户将创建哪些特定类型的形状。You do not know at compile time which specific types of shapes the user will create. 但应用程序必须跟踪创建的所有类型的形状,并且必须更新这些形状以响应用户鼠标操作。However, the application has to keep track of all the various types of shapes that are created, and it has to update them in response to user mouse actions. 你可以使用多态性通过两个基本步骤解决这一问题:You can use polymorphism to solve this problem in two basic steps:

  1. 创建一个类层次结构,其中每个特定形状类均派生自一个公共基类。Create a class hierarchy in which each specific shape class derives from a common base class.
  2. 使用虚方法通过对基类方法的单个调用来调用任何派生类上的相应方法。Use a virtual method to invoke the appropriate method on any derived class through a single call to the base class method.

首先,创建一个名为 Rectangle``Shape 的基类,并创建一些派生类,例如 Triangle``Circle、 和 。First, create a base class called Shape, and derived classes such as Rectangle, Circle, and Triangle. Shape 类提供一个名为 Draw 的虚拟方法,并在每个派生类中重写该方法以绘制该类表示的特定形状。Give the Shape class a virtual method called Draw, and override it in each derived class to draw the particular shape that the class represents. 创建 List<Shape> 对象,并向其添加 CircleTriangleRectangleCreate a List<Shape> object and add a Circle, Triangle, and Rectangle to it.

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
public class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
public class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

若要更新绘图图面,请使用 foreach 循环对该列表进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法。To update the drawing surface, use a foreach loop to iterate through the list and call the Draw method on each Shape object in the list. 虽然列表中的每个对象都具有声明类型 Shape,但调用的将是运行时类型(该方法在每个派生类中的重写版本)。Even though each object in the list has a declared type of Shape, it's the run-time type (the overridden version of the method in each derived class) that will be invoked.

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
*/

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 ObjectIn C#, every type is polymorphic because all types, including user-defined types, inherit from Object.

多形性概述Polymorphism overview

虚拟成员Virtual members

当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。When a derived class inherits from a base class, it gains all the methods, fields, properties, and events of the base class. 派生类的设计器可以针对虚拟方法的行为做出不同的选择:The designer of the derived class can different choices for the behavior of virtual methods:

  • 派生类可以重写基类中的虚拟成员,并定义新行为。The derived class may override virtual members in the base class, defining new behavior.
  • 派生类继承最接近的基类方法而不重写方法,同时保留现有的行为,但允许进一步派生的类重写方法。The derived class inherit the closest base class method without overriding it, preserving the existing behavior but enabling further derived classes to override the method.
  • 派生类可以定义隐藏基类实现的成员的新非虚实现。The derived class may define new non-virtual implementation of those members that hide the base class implementations.

仅当基类成员声明为 virtualabstract 时,派生类才能重写基类成员。A derived class can override a base class member only if the base class member is declared as virtual or abstract. 派生成员必须使用 override 关键字显式指示该方法将参与虚调用。The derived member must use the override keyword to explicitly indicate that the method is intended to participate in virtual invocation. 以下代码提供了一个示例:The following code provides an example:

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; }
    }
}

字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。Fields cannot be virtual; only methods, properties, events, and indexers can be virtual. 当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。When a derived class overrides a virtual member, that member is called even when an instance of that class is being accessed as an instance of the base class. 以下代码提供了一个示例:The following code provides an example:

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; }
    }
}

虚方法和属性允许派生类扩展基类,而无需使用方法的基类实现。Virtual methods and properties enable derived classes to extend a base class without needing to use the base class implementation of a method. 有关详细信息,请参阅使用 Override 和 New 关键字进行版本控制For more information, see Versioning with the Override and New Keywords. 接口提供另一种方式来定义将实现留给派生类的方法或方法集。An interface provides another way to define a method or set of methods whose implementation is left to derived classes. 有关详细信息,请参阅接口For more information, see Interfaces.

使用新成员隐藏基类成员Hide base class members with new members

如果希望派生类具有与基类中的成员同名的成员,则可以使用 new 关键字隐藏基类成员。If you want your derived class to have a member with the same name as a member in a base class, you can use the new keyword to hide the base class member. new 关键字放置在要替换的类成员的返回类型之前。The new keyword is put before the return type of a class member that is being replaced. 以下代码提供了一个示例:The following code provides an example:

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; }
    }
}

通过将派生类的实例强制转换为基类的实例,可以从客户端代码访问隐藏的基类成员。Hidden base class members may be accessed from client code by casting the instance of the derived class to an instance of the base class. 例如:For example:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

阻止派生类重写虚拟成员Prevent derived classes from overriding virtual members

无论在虚拟成员和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员都是虚拟的。Virtual members remain virtual, regardless of how many classes have been declared between the virtual member and the class that originally declared it. 如果类 A 声明了一个虚拟成员,类 BA 派生,类 C 从类 B 派生,则不管类 B 是否为虚拟成员声明了重写,类 C 都会继承该虚拟成员,并可以重写它。If class A declares a virtual member, and class B derives from A, and class C derives from B, class C inherits the virtual member, and may override it, regardless of whether class B declared an override for that member. 以下代码提供了一个示例:The following code provides an example:

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

派生类可以通过将重写声明为 sealed 来停止虚拟继承。A derived class can stop virtual inheritance by declaring an override as sealed. 停止继承需要在类成员声明中的 override 关键字前面放置 sealed 关键字。Stopping inheritance requires putting the sealed keyword before the override keyword in the class member declaration. 以下代码提供了一个示例:The following code provides an example:

public class C : B
{
    public sealed override void DoWork() { }
}

在上一个示例中,方法 DoWork 对从 C 派生的任何类都不再是虚拟方法。In the previous example, the method DoWork is no longer virtual to any class derived from C. 即使它们转换为类型 B 或类型 A,它对于 C 的实例仍然是虚拟的。It's still virtual for instances of C, even if they're cast to type B or type A. 通过使用 new 关键字,密封的方法可以由派生类替换,如下面的示例所示:Sealed methods can be replaced by derived classes by using the new keyword, as the following example shows:

public class D : C
{
    public new void DoWork() { }
}

在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWorkIn this case, if DoWork is called on D using a variable of type D, the new DoWork is called. 如果使用类型为 CBA 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 CDoWork 实现。If a variable of type C, B, or A is used to access an instance of D, a call to DoWork will follow the rules of virtual inheritance, routing those calls to the implementation of DoWork on class C.

从派生类访问基类虚拟成员Access base class virtual members from derived classes

已替换或重写某个方法或属性的派生类仍然可以使用 base 关键字访问基类的该方法或属性。A derived class that has replaced or overridden a method or property can still access the method or property on the base class using the base keyword. 以下代码提供了一个示例:The following code provides an example:

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();
    }
}

有关详细信息,请参阅 baseFor more information, see base.

备注

建议虚拟成员在它们自己的实现中使用 base 来调用该成员的基类实现。It is recommended that virtual members use base to call the base class implementation of that member in their own implementation. 允许基类行为发生使得派生类能够集中精力实现特定于派生类的行为。Letting the base class behavior occur enables the derived class to concentrate on implementing behavior specific to the derived class. 未调用基类实现时,由派生类负责使它们的行为与基类的行为兼容。If the base class implementation is not called, it is up to the derived class to make their behavior compatible with the behavior of the base class.

本节内容In this section

请参阅See also