Поделиться через


Управление версиями с помощью переопределения и новых ключевых слов (руководство по программированию на C#)

Язык C# разработан таким образом, чтобы версии между базовыми и производными классами в разных библиотеках могли развиваться, сохраняя обратную совместимость. Это означает, например, что введение нового члена в базовый класс с тем же именем, что и член в производном классе, полностью поддерживается C# и не приводит к неожиданному поведению. Это также означает, что класс должен явно указывать, предназначен ли метод для переопределения унаследованного метода или является ли метод новым методом, который скрывает аналогично именованный метод.

В C#производные классы могут содержать методы с тем же именем, что и методы базового класса.

  • Если метод в производном классе не предшествует новым или переопределенным ключевым словам, компилятор выдает предупреждение, и метод будет вести себя так, как если new бы ключевое слово присутствовало.

  • Если метод в производном классе предшествует new ключевому слову, метод определяется как независимый от метода в базовом классе.

  • Если метод в производном классе предшествует override ключевому слову, объекты производного класса вызывают этот метод вместо метода базового класса.

  • Чтобы применить ключевое слово override к методу в производном классе, метод базового класса должен быть определён как virtual.

  • Метод базового класса можно вызвать из производного класса с помощью ключевого base слова.

  • Ключевые слова override, virtual и new также могут применяться к свойствам, индексаторам и событиям.

По умолчанию методы C# не являются виртуальными. Если метод объявлен как виртуальный, любой класс, наследующий метод, может реализовать собственную версию. Чтобы сделать метод виртуальным, virtual модификатор используется в объявлении метода базового класса. Затем производный класс может переопределить базовый виртуальный метод с помощью override ключевого слова или скрыть виртуальный метод в базовом классе с помощью ключевого new слова. Если ни ключевое слово override, ни ключевое слово new не указаны, компилятор выдаст предупреждение, и метод в производном классе скроет метод в базовом классе.

Чтобы продемонстрировать это на практике, предположим, что компания A создала класс с именем GraphicsClass, который использует ваша программа. Ниже приведено GraphicsClassследующее:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
}

Ваша компания использует этот класс, и вы используете его для получения собственного класса, добавив новый метод:

class YourDerivedGraphicsClass : GraphicsClass
{
    public void DrawRectangle() { }
}

Приложение используется без проблем, пока компания A не выпускает новую версию GraphicsClass, которая напоминает следующий код:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
    public virtual void DrawRectangle() { }
}

Новая версия GraphicsClass теперь содержит метод с именем DrawRectangle. Изначально ничего не происходит. Новая версия по-прежнему бинарно совместима со старой версией. Любая развернутая программа будет работать, даже если новый класс установлен на этих компьютерных системах. Все существующие вызовы метода DrawRectangle будут продолжать ссылаться на вашу версию в производном классе.

Однако после повторной компиляции приложения с помощью новой версии GraphicsClassвы получите предупреждение от компилятора CS0108. Это предупреждение информирует вас о том, что необходимо обдумать, как вы хотите, чтобы метод DrawRectangle вел себя в вашем приложении.

Если вы хотите, чтобы ваш метод переопределял метод нового базового класса, используйте ключевое слово override.

class YourDerivedGraphicsClass : GraphicsClass
{
    public override void DrawRectangle() { }
}

Ключевое override слово гарантирует, что любые объекты, производные от YourDerivedGraphicsClass, будут использовать версию класса, полученную от производного DrawRectangle. Объекты, производные от YourDerivedGraphicsClass, по-прежнему могут получить доступ к версии базового класса с помощью ключевого слова base DrawRectangle.

base.DrawRectangle();

Если вы не хотите, чтобы ваш метод переопределял новый метод базового класса, примените следующие рекомендации. Чтобы избежать путаницы между двумя методами, можно переименовать метод. Это может требовать много времени, нести риск ошибок, а также быть непрактично в некоторых случаях. Однако если проект относительно мал, можно использовать параметры рефакторинга Visual Studio для переименования метода. Дополнительные сведения см. в разделе "Рефакторинг классов и типов" (конструктор классов).

Кроме того, предупреждение можно предотвратить с помощью ключевого слова new в определении производного класса:

class YourDerivedGraphicsClass : GraphicsClass
{
    public new void DrawRectangle() { }
}

new Использование ключевого слова сообщает компилятору, что определение скрывает определение, содержащееся в базовом классе. Это поведение по умолчанию.

Переопределение и выбор метода

При именовавшемся методе в классе компилятор C# выбирает лучший метод для вызова, если с вызовом совместимо несколько методов, например при наличии двух методов с одинаковым именем и параметрами, совместимыми с переданным параметром. Следующие методы будут совместимы:

public class Derived : Base
{
    public override void DoWork(int param) { }
    public void DoWork(double param) { }
}

Когда DoWork вызывается на экземпляре Derived, компилятор C# сначала попытается выполнить вызов, совместимый с версиями DoWork, изначально объявленными на Derived. Методы переопределения не считаются объявленными в классе, они являются новыми реализациями метода, объявленного в базовом классе. Только если компилятор C# не может сопоставить вызов метода с исходным методом на Derived, он попытается сопоставить вызов с переопределенным методом с тем же именем и совместимыми параметрами. Рассмотрим пример.

int val = 5;
Derived d = new();
d.DoWork(val);  // Calls DoWork(double).

Поскольку переменная val может быть неявно преобразована в тип double, компилятор C# вызывает DoWork(double) вместо DoWork(int). Это можно избежать двумя способами. Во-первых, не объявляйте новые методы с тем же именем, что и виртуальные методы. Во-вторых, компилятор C# может вызвать виртуальный метод, выполнив поиск в списке методов базового класса путем приведения экземпляра Derived в Base. Так как метод является виртуальным, будет вызвана реализация DoWork(int) на Derived. Рассмотрим пример.

((Base)d).DoWork(val);  // Calls DoWork(int) on Derived.

Для получения дополнительных примеров new и override см. раздел «Когда следует использовать ключевые слова override и new».

См. также