- •Структуры и классы
- •Наследование реализации
- •Виртуальные методы
- •Сокрытие методов
- •Вызов базовых версий функций
- •Абстрактные классы и функции
- •Запечатанные классы и методы
- •Конструкторы производных классов
- •Добавление в иерархию конструктора
- •Добавление в иерархию конструкторов с параметрами
- •Интерфейсы
- •IDisposable — сравнительно простой интерфейс, потому что в нем определен только один метод. Большинство интерфейсов содержат гораздо большее количество методов.
- •Определение и реализация интерфейсов
- •Производные интерфейсы
Сокрытие методов
Если методы с одинаковой сигнатурой объявлены и в базовом, и в унаследованном классе, но при этом не указаны, соответственно, как virtual и override, то говорят, что версия метода в классе-наследнике скрывает версию метода базового класса.
В большинстве случаев требуется переопределять методы, а не скрывать их. Скрывая методы, вы рискуете вызывать "неверный" метод для экземпляра данного класса. Однако, как показано в следующем примере, синтаксис С# спроектирован так, чтобы гарантировать, что разработчик будет предупрежден об этой потенциальной проблеме во время компиляции, тем самым обеспечивая возможность более безопасного сокрытия методов, если это действительно нужно. Это также дает преимущества разработчикам библиотек классов при работе с разными версиями.
Предположим, что имеется класс HisBaseClass:
class HisBaseClass
{
// разнообразные члены
}
В какой-то момент в будущем вы напишете класс-наследник, добавляющий некоторую функциональность к HisBaseClass. В частности, добавите метод MyGroovyMethod(), которого нет в базовом классе:
class MyDerivedClass: HisBaseClass
{
public int MyGroovyMethod()
{
// некая превосходная реализация
return 0;
}
}
Годом позже вы решите расширить функциональность базового класса. Случайно вы добавите метод, также именуемый MyGroovyMethod(), имеющий то же имя и сигнатуру, что и в наследнике, но, возможно, решающий какую-то другую задачу. Компилируя код, использующий новую версию базового класса, чвы получаете потенциальный конфликт, поскольку программа не знает, какой именно метод вызывать. Это совершенно корректно с точки зрения С#, но поскольку ваш MyGroovyMethod() никак не связан с версией MyGroovyMethod() из базового класса, то при запуске этого кода вы не получите того, чего ожидали. К счастью, язык С# спроектирован так, что прекрасно справляется с конфликтами подобного рода.
В таких случаях при компиляции С# генерирует предупреждение. Оно напомнит о необходимости применения ключевого слова new при выражении намерения сокрыть метод базового класса:
class MyDerivedClass: HisBaseClass
{
public new int MyGroovyMethod()
{
// некая превосходная реализация
return 0;
}
}
Но поскольку версия MyGroovyMethod() не объявлена как new, компилятор укажет на тот факт, что она скрывает метод базового класса, несмотря на отсутствие указания делать это, выдав предупреждение (это произойдет независимо от того, объявлен метод MyGroovyMethod() виртуальным или нет). Если хотите, можете переименовать свою версию метода. И это будет наилучшим решением, поскольку оно исключает будущую путаницу. Однако если по каким-то причинам вы решите не переименовывать такой метод (например, вы поставляете свой код в виде библиотек другим компаниям, а потому не можете изменять имена методов), то весь существующий клиентский код будет работать корректно, выбирая вашу версию MyGroovyMethod(). Это объясняется тем, что любой существующий код, который обращается к этому методу, должен делать это через ссылку на MyDerivedClass (или на будущий класс-наследник).
Существующий код не может обращаться к этому методу через ссылку на HisBaseClass будет выдана ошибка при компиляции более ранней версии HisBaseClass. Проблема может возникнуть только в клиентском коде. Компилятор С# ведет себя так, что вы получаете предупреждение о потенциальных проблемах, которые могут возникнуть в будущем коде. На это предупреждение нужно обязательно обратить внимание и позаботиться о том, чтобы не пытаться вызывать вашу версию MyGroovyMethod() через любую ссылку на HisBaseClass в любом коде, который будет написан позже. Тем не менее, весь существующий код будет работать нормально. Может показаться, что это довольно тонкий момент, но в то же время это красноречивый пример того, как С# может справляться с разными версиями классов.