Полиморфизм (Руководство по программированию на C#)Polymorphism (C# Programming Guide)

Полиморфизм часто называется третьим столпом объектно-ориентированного программирования после инкапсуляции и наследования.Polymorphism is often referred to as the third pillar of object-oriented programming, after encapsulation and inheritance. Полиморфизм — слово греческого происхождения, означающее "многообразие форм" и имеющее несколько аспектов.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 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. Таким образом, в исходном коде можно вызвать метод на базовом классе и привести версию производного класса метода, который необходимо выполнить.Thus 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.

Для начала создайте базовый класс с именем Shape и производные классы, например Rectangle, Circle и Triangle.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> и добавьте в него круг, треугольник и прямоугольник.Create a List<Shape> object and add a Circle, Triangle and Rectangle to it. Для обновления поверхности рисования используйте цикл foreach, чтобы выполнить итерацию списка и вызвать метод Draw на каждом объекте Shape в списке.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 is the run-time type (the overridden version of the method in each derived class) that will be invoked.

using System;
using System.Collections.Generic;

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

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

class Program
{
    static void Main(string[] args)
    {
        // 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();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* 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# каждый тип является полиморфным, так как все типы, включая пользовательские, наследуют Object.In 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 choose whether to

  • переопределение виртуальных членов в базовом классе;override virtual members in the base class,

  • наследование метода ближайшего базового класса без переопределения;inherit the closest base class method without overriding it

  • определение новой, невиртуальной реализации тех членов, которые скрывают реализации базового класса.define new non-virtual implementation of those members that hide the base class implementations

Производный класс может переопределить член базового класса, только если последний будет объявлен виртуальным или абстрактным.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:

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

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

Виртуальные методы и свойства позволяют производным классам расширять базовый класс без необходимости использовать реализацию базового класса метода.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.

Сокрытие членов базового класса новыми членамиHiding Base Class Members with New Members

Если вам нужно, чтобы производный член имел такое же имя, как и член в базовом классе, но вы не хотите, чтобы он участвовал в виртуальном вызове, используйте ключевое слово new.If you want your derived member to have the same name as a member in a base class, but you do not want it to participate in virtual invocation, you can use the new keyword. Ключевое слово 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 can still 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.

Защита виртуальных членов от переопределения производными классамиPreventing Derived Classes from Overriding Virtual Members

Виртуальные члены остаются виртуальными на неограниченный срок независимо от количества классов, объявленных между виртуальным членом и классом, который объявил его изначально.Virtual members remain virtual indefinitely, regardless of how many classes have been declared between the virtual member and the class that originally declared it. Если класс А объявляет виртуальный член, класс В производится из класса А, а класс С — из класса В, то класс С наследует виртуальный член и получает возможность переопределить его независимо от того, объявляет ли класс В переопределение этого члена.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 has the option to 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() { }
}

Производный класс может остановить виртуальное наследование, объявив переопределение как запечатанное.A derived class can stop virtual inheritance by declaring an override as sealed. Для этого в объявление члена класса необходимо вставить ключевое слово sealed перед ключевым словом override.This 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. Он по-прежнему будет виртуальным для экземпляров C, даже если они будут приведены к типу B или A. Запечатанные методы можно заменить производными классами с помощью ключевого слова new, как показано в следующем примере:In the previous example, the method DoWork is no longer virtual to any class derived from C. It is still virtual for instances of C, even if they are cast to type B or type A. 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() { }
}

В этом случае, если DoWork вызывается на D с помощью переменной типа D, вызывается новый DoWork.In this case, if DoWork is called on D using a variable of type D, the new DoWork is called. Если переменная типа C, B или A используется для доступа к экземпляру D, вызов DoWork будет выполняться по правилам виртуального наследования и направлять эти вызовы на реализацию DoWork на классе C.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.

Доступ к виртуальным членам базового класса из производных классовAccessing 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();
    }
}

Дополнительные сведения см. в разделе base.For 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