Виртуальные методы, свойства и индексаторы
Полиморфизм предоставляет подклассу способ определения собственной версии метода, определенного в его базовом классе, с использованием процесса, который называется переопределением метода (method overriding). Чтобы пересмотреть текущий дизайн, нужно понять значение ключевых слов virtual и override.
Виртуальным называется такой метод, который объявляется как virtual в базовом классе. Виртуальный метод отличается тем, что он может быть переопределен в одном или нескольких производных классах. Следовательно, у каждого производного класса может быть свой вариант виртуального метода. Кроме того, виртуальные методы интересны тем, что именно происходит при их вызове по ссылке на базовый класс. В этом случае средствами языка C# определяется именно тот вариант виртуального метода, который следует вызывать, исходя из типа объекта, к которому происходит обращение по ссылке, причем это делается во время выполнения. Поэтому при ссылке на разные типы объектов выполняются разные варианты виртуального метода. Иными словами, вариант выполняемого виртуального метода выбирается по типу объекта, а не по типу ссылки на этот объект.
Так, если базовый класс содержит виртуальный метод и от него получены производные классы, то при обращении к разным типам объектов по ссылке на базовый класс выполняются разные варианты этого виртуального метода.
Метод объявляется как виртуальный в базовом классе с помощью ключевого слова virtual, указываемого перед его именем. Когда же виртуальный метод переопределяется в производном классе, то для этого используется модификатор override. А сам процесс повторного определения виртуального метода в производном классе называется переопределением метода. При переопределении метода - имя, возвращаемый тип и сигнатура переопределяющего метода должны быть точно такими же, как и у того виртуального метода, который переопределяется. Кроме того, виртуальный метод не может быть объявлен как static или abstract.
Переопределение метода служит основанием для воплощения одного из самых эффективных в C# принципов: динамической диспетчеризации методов, которая представляет собой механизм разрешения вызова во время выполнения, а не компиляции. Значение динамической диспетчеризации методов состоит в том, что именно благодаря ей в C# реализуется динамический полиморфизм.
Если при наличии многоуровневой иерархии виртуальный метод не переопределяется в производном классе, то выполняется ближайший его вариант, обнаруживаемый вверх по иерархии.
И еще одно замечание: свойства также подлежат модификации ключевым словом virtual и переопределению ключевым словом override. Это же относится и к индексаторам.
Давайте рассмотрим пример использования виртуальных методов, свойств и индексаторов:
// Реализуем класс содержащий информацию о шрифтах // и использующий виртуальные методы, свойства и индексаторы using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { // Базовый класс class Font { string TypeFont; short FontSize; public Font() { TypeFont = "Arial"; FontSize = 12; } public Font(string TypeFont, short FontSize) { this.TypeFont = TypeFont; this.FontSize = FontSize; } public string typeFont { get { return TypeFont; } set { TypeFont = value; } } public short fontSize { get { return FontSize; } set { FontSize = value; } } // Создаем виртуальный метод public virtual string FontInfo(Font obj) { string s = "Информация о шрифте: \n------------------\n\n" + "Тип шрифта: " + typeFont + "\nРазмер шрифта: " + fontSize + "\n"; return s; } } // Производный класс 1 уровня class ColorFont : Font { byte Color; public ColorFont(byte Color, string TypeFont, short FontSize) : base(TypeFont, FontSize) { this.Color = Color; } // Переопределение для виртуального метода public override string FontInfo(Font obj) { // Используется ссылка на метод определенный в базовом классе Font return base.FontInfo(obj) + "Цвет шрифта: " + Color + "\n"; } // Создадим виртуальное свойство public virtual byte color { set { Color = value; } get { return Color; } } } // Производный класс 2 уровня class GradientColorFont : ColorFont { char TypeGradient; public GradientColorFont(char TypeGradient, byte Color, string TypeFont, short FontSize) : base(Color, TypeFont, FontSize) { this.TypeGradient = TypeGradient; } // Опять переопределяем виртуальный метод public override string FontInfo(Font obj) { // Используется ссылка на метод определенный в производном классе FontColor return base.FontInfo(obj) + "Тип градиента: " + TypeGradient + "\n\n"; } // Переопределим виртуальное свойство public override byte color { get { return base.color; } set { if (value < 10) base.color = 0; else base.color = (byte)(value - 0x0A); } } } // Еще один производный класс 1 уровня class FontStyle : Font { string style; public FontStyle(string style, string TypeFont, short FontSize) : base (TypeFont, FontSize) { this.style = style; } // Данный класс не переопределяет виртуальный метод // поэтому при вызове метода FontInfo () // вызывается метод созданный в базовом классе } class Program { static void Main() { ColorFont font1 = new ColorFont(Color: 0xCF, TypeFont: "MS Trebuchet", FontSize: 16); Console.WriteLine(font1.FontInfo(font1)); GradientColorFont font2 = new GradientColorFont(Color: 0xFF, TypeFont: "Times New Roman", FontSize: 10, TypeGradient: 'R'); Console.WriteLine(font2.FontInfo(font2)); font2.color = 0x2F; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Видоизмененный цвет font2"); Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(font2.FontInfo(font2)); FontStyle font3 = new FontStyle(style: "oblique", TypeFont: "Calibri", FontSize: 16); Console.WriteLine(font3.FontInfo(font3)); Console.ReadLine(); } } }
Давайте рассмотрим данный пример более подробно. В базовом классе Font инкапсулируется виртуальный метод FontInfo (), возвращающий информацию о шрифте. В производном классе FontColor данный метод переопределяется с помощью ключевого слова override, поэтому при создании экземпляра данного класса и вызова метода FontInfo() в исходную информацию возвращается помимо первоначальных данных еще и цвет шрифта. Затем данный метод вновь переопределяется в классе GradientColorFont, унаследованном от класса FontColor. Обратите внимание, что здесь переопределяется не исходный метод базового класса Font, а уже переопределенный метод класса FontColor. В этом и заключается принцип динамического полиморфизма!
Так же обратите внимание, что в данном примере используется виртуальное свойство color, принцип использования которого аналогичен использованию виртуального метода.
Комментарии