Украина, Рубежное, РФВНУ им. В.Даля, 2007 |
Лекция 11: Расширенные возможности полиморфизма в языке C#
Рассмотрим особенности реализации сокрытия данных в языке программирования C#.
Заметим, что в языке C# существует возможность описания объектов как экземпляров в составе подкласса с использованием зарезервированного слова new. При этом происходит сокрытие одноименных наследуемых экземпляров в подклассах.
Проиллюстрируем особенности использования механизма сокрытия данных в языке программирования C# следующим фрагментом программы:
class A { public int x; public void F() { ... } public virtual void G() { ... } } class B : A { public new int x; public new void F() { ... } public new void G() { ... } } B b = new B(); b.x = ...; // имеет доступ к B.x b.F(); ... b.G(); // вызывает B.F и B.G ((A)b).x = ...; // имеет доступ к A.x ((A)b).F(); ... ((A)b).G(); // вызывает A.F и A.G
Как видно из приведенного фрагмента, базовый класс A и производный класс B характеризуются общедоступными полем x и методами F и G. Особенности доступа к элементам классов приведены в комментариях. Заметим, что при описании элементов производного класса используется зарезервированное слово new.
Рассмотрим особенности реализации сложного динамического связывания в языке программирования C#. Под сложным связыванием будем понимать связывание с сокрытием (hiding) данных. Для иллюстрации особенностей использования механизма сокрытия данных воспользуемся следующим фрагментом программы на языке C#. Приведем текст примера:
class A { public virtual void WhoAreYou() { Console.WriteLine("I am an A"); } } class B : A { public override void WhoAreYou() { Console.WriteLine("I am a B"); } } class C : B { public new virtual void WhoAreYou() { Console.WriteLine("I am a C"); } } class D : C { public override void WhoAreYou() { Console.WriteLine("I am a D"); } } C c = new D(); c.WhoAreYou(); // "I am a D" A a = new D(); a.WhoAreYou(); // "I am a B"
Как мы видим, фрагмент программы содержит описания общедоступных классов: базового класса A и производных классов B и C с иерархией C ISA B ISA A. Каждый из классов характеризуется единственным общедоступным методом WhoAreYou для вывода в стандартный поток диагностического сообщения о принадлежности к данному классу. Заметим, что методы WhoAreYou для классов A и C описаны как виртуальные, причем в последнем описании используется зарезервированное слово new. Метод WhoAreYou для класса B описан как замещенный.
Продолжение фрагмента программы содержит описание общедоступного класса D как производного от C.
Таким образом, иерархия классов принимает вид: D ISA C ISA B ISA A. Класс D характеризуется аналогичным предыдущим классам общедоступным методом WhoAreYou для вывода в стандартный поток диагностического сообщения о принадлежности к данному классу. Заметим, что метод WhoAreYou для класса D является замещенным (но не виртуальным ), и в его описании зарезервированное слово new не используется.
При инициализации объектов c и a как экземпляров класса D применение "отладочных" методов дает результат "I am a D" для объекта c и результат "I am a B" для объекта a. Полученный результат объясняется расположением описателей методов override в иерархии классов (более верхний описатель имеет более высокий приоритет).
Дальнейшим развитием динамического связывания в языке программирования C# является реализация механизма вызова методов с приоритетами.
При реализации рассматриваемого механизма на взаимосвязанные семейства методов накладывается ряд дополнительных ограничений.
Прежде всего, требуется обеспечить идентичность описаний методов в группе с приоритетами. При этом для каждого из методов должны выполняться следующие условия.
Во-первых, у всех методов семейства для вызова с приоритетами должны быть одинаковыми как количество, так и типы параметров. Типы функций не должны составлять исключение из этого правила. Во-вторых, все методы семейства для вызова с приоритетами должны иметь одинаковые области видимости (например, каждый из методов семейства должен иметь описатель public или, скажем, protected ; наличие в семействе методов с разными описателями области видимости не допускается).
Кроме того, необходимо отметить, что свойства и индексаторы для семейства методов с приоритетами также могут иметь приоритет (с использованием описателя override ). Поскольку метод с приоритетами является принципиально динамическим объектом языка программирования, статические методы не могут описываться как методы с приоритетами.
Заметим, что только методы, описанные как виртуальные ( virtual ), могут иметь приоритет в производных классах.
Наконец, при задании методов с приоритетами необходимо использовать описатель override .
Проиллюстрируем особенности использования механизма методов с приоритетами в языке C# следующим фрагментом программы:
class A { public void F() { ... } // может иметь приоритет public virtual void G() { ... } // может иметь приоритет // в подклассе } class B : A { public void F() { ... } // предупреждение: скрывается // производный метод F(). // Необходимо использовать // оператор new. public void G() { ... } // предупреждение: скрывается // производныйметод G(). // Необходимо использовать // оператор new. public override void G() { // ok: перекрывает приоритетом // производныйметод G ... base.G(); // вызов производного // метода G() } }
Как видно из приведенного примера, фрагмент программы включает описания базового класса A и производного класса B, каждый из которых содержит общедоступные методы F и G. При этом метод G класса использует описатель virtual. Как следует из комментариев, при задании методов F и G в производном классе B без использования описателя override происходит сокрытие производных методов F и G. Таким образом, для корректной работы F и G в классе необходимо использовать оператор new. В случае применения описателя override при задании метода G в классе B происходит замещение приоритетным методом G, и, таким образом, оператор new не требуется.