Классы и объекты C#: сокрытие методов и свойств

На данные момент нам известно как в C# работает перегрузка (overload) и переопределение (override) методов и свойств классов. Однако, на этом возможности C# по изменению функциональности базовых свойств и методов в классах не ограничиваются.  Также, одним из способов изменить функциональность метода или свойства, унаследованного от базового класса, является сокрытие (hiding или shadowing). И сегодня мы будем рассматривать именно этот вопрос — сокрытие методов и свойств классов в C#.

Сокрытие методов и свойств класса в C#

Вначале рассмотрим, что из себя представляет сокрытие в C#, в принципе. Сокрытие в C# — это определение в классе-наследнике метода или свойства, которые полностью соответствуют по имени и набору параметров методу или свойству базового класса (родителя).  Про отличия в переопределении и сокрытии в C# поговорим ниже. Пока же нам достаточно знать, что для сокрытия методов или свойств класса в C# применяется ключевое слово new. Например:

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }


    public void Display()
    {
        Console.WriteLine($"{FirstName} {LastName}");
    }
}


class Employee : Person
{
    public string Company { get; set; }
    public Employee(string firstName, string lastName, string company): base(firstName, lastName)
    {
        Company = company;
    }
    public new void Display()
    {
        Console.WriteLine($"{FirstName} {LastName} работает в {Company}");
    }
}

У класса Person (человек) определено два свойства и метод Display(), который выводит в консоль имя и фамилию человека. Обратите внимание на то, что метод не помечен как виртуальный (virtual), то есть переопределить мы его не можем.

Класс Employee (работник) наследует все свойства и методы от базового класса Person, но также имеет собственное свойство Company, а базовый метод Display() скрыт — использовано ключевое слово new. Таким образом, мы скрыли метод класса-родителя и определили для него новую функциональность — помимо имени и фамилии теперь в консоль также будет выводиться и название компании.

Используем разработанные выше классы в программе:

        Person bob = new Person("Bob", "Robertson");
        bob.Display();      // Bob Robertson

        Employee tom = new Employee("Tom", "Smith", "Microsoft");
        tom.Display();      // Tom Smith работает в Microsoft

        Console.ReadKey();

Результат работы программы:

Bob Robertson

Tom Smith работает в Microsoft

Аналогичным образом работает и сокрытие свойств в C#. Например:

class Person
{
    protected string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}
class Employee : Person
{
    public new string Name
    {
        get { return "Employee " + base.Name; }
        set { name = value; }
    }
}

Здесь мы скрыли свойство Name.  Опять же, сокрытие методов и свойств в C# не означает, что мы не можем обращаться к базовым реализациям членов класса. Для доступа к базовым методам и свойствам классов, даже, если они скрыты, также используется ключевое слово base. Более того, мы можем применять механизм сокрытия даже к переменным и константам:

class ExampleBase
{
    public readonly int x = 10;
    public const int G = 5;
}
class ExampleDerived : ExampleBase
{
    public new readonly int x = 20;
    public new const int G = 15;
}

При этом если мы хотим обратиться именно к реализации свойства или метода в базовом классе, то опять же мы можем использовать ключевое слово base и через него обращаться к функциональности базового класса.

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

class ExampleBase
{
    public readonly int x = 10;
    public const int G = 5;
}
class ExampleDerived : ExampleBase
{
    public new readonly int x = 20;
    public new const int G = 15;
}

Различие переопределения и сокрытия методов

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

Переопределение

Рассмотрим следующий пример переопределения метода:

class Person
{
    public string Name { get; set; }
    public Person(string name)
    {
        Name = name;
    }
    public virtual void Display()
    {
        Console.WriteLine(Name);
    }
}
class Employee : Person
{
    public string Company { get; set; }
    public Employee(string name, string company)
        : base(name)
    {
        Company = company;
    }


    public override void Display()
    {
        Console.WriteLine($"{Name} работает в {Company}");
    }
}

Создадим объект класса-наследника (Employee) и передадим его переменной с типом родителя (Person)

Person tom = new Employee("Tom", "Microsoft");
tom.Display();      // Tom работает в Microsoft

При вызове tom.Display() выполняется реализация метода Display из класса Employee. Что для нас, в принципе, ожидаемо. Какова механика работы с виртуальными методами в C#:

  1. Для работы с виртуальными методами компилятор формирует таблицу виртуальных методов (Virtual Method Table или VMT). В эту таблицу записываются адреса всех виртуальных методов класса, то есть, для каждого класса формируется своя VMT.
  2. Когда создается объект класса, то компилятор передает в конструктор объекта код, который связывает наш объект с таблицей VMT.
  3. При вызове виртуального метода из объекта:
    1. берется адрес его таблицы VMT.
    2. из VMT извлекается адрес метода и ему передается управление.

Весь процесс выбора той или иной реализации метода производится во время выполнения программы. Именно так и выполняются виртуальные методы в C#.

Исходя из этого, следует учитывать, следующее:

так как среде выполнения вначале необходимо получить из таблицы VMT адрес нужного метода, то это немного замедляет выполнение программы

Сокрытие

Теперь возьмем те же классы Person и Employee, но вместо переопределения используем сокрытие:

class Person
{
    public string Name { get; set; }
    public Person(string name)
    {
        Name = name;
    }


    public void Display()
    {
        Console.WriteLine(Name);
    }
}


class Employee : Person
{
    public string Company { get; set; }
    public Employee(string name, string company)
            : base(name)
    {
        Company = company;
    }
    public new void Display()
    {
        Console.WriteLine($"{Name} работает в {Company}");
    }
}

Посмотрим, что получится в итоге в следующем случае:

Person tom = new Employee("Tom", "Microsoft");
tom.Display();      // Tom

Почему так произошло? Всё дело в том, что класс Employee никак не переопределяет метод Display, унаследованный от базового класса, а фактически определяет новый метод, то есть VMT не создается. Поэтому при вызове tom.Display() вызывается метод Display из класса Person.

Итого

Сегодня мы рассмотрели ещё один механизм изменения функциональности свойств и методов классов в C# — сокрытие. Этот механизм позволяет создавать в производном классе свойства и методы с сигнатурой как и у членов базового класса и, при этом, в базовом классе не используется ключевое слово virtual. Что касается того, стоит ли использовать сокрытие методов в C# при разработке приложений, то, на мой взгляд, механизм сокрытия стоит использовать с особой осторожностью и только там, где это использование полностью оправдано. В остальных же случаях лучше использовать переопределение или перегрузку методов и свойств.

 

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии