Upcasting, downcasting

Допустим, у нас есть следующая иерархия классов:

class Person
{
    public string Name { get; set; }
    public Person(string name)
    {
        Name = name;
    }
    public void Print()
    {
        Console.WriteLine($"Person {Name}");
    }
}
 
class Employee : Person
{
    public string Company { get; set; }
    public Employee(string name, string company) : base(name)
    {
        Company = company;
    }
}
 
class Client : Person
{
    public string Bank { get; set; }
    public Client(string name, string bank) : base(name)
    {
        Bank = bank;
    }
}

В этой иерархии классов мы можем проследить следующую цепь наследования: Object (все классы неявно наследуются от типа Object) -> Person -> Employee|Client.

Иерархия наследования в языке C#

Причем в этой иерархии классов базовые типы находятся вверху, а производные типы — внизу.

Восходящие преобразования. Upcasting

Объекты производного типа (который находится внизу иерархии) в то же время представляют и базовый тип. Например, объект Employee в то же время является и объектом класса Person. Что в принципе естественно, так как каждый сотрудник (Employee) является человеком (Person). И мы можем написать, например, следующим образом:

Employee employee = new Employee("Tom", "Microsoft");
Person person = employee;   // преобразование от Employee к Person
 
Console.WriteLine(person.Name); 

В данном случае переменной person, которая представляет тип Person, присваивается ссылка на объект Employee. Но чтобы сохранить ссылку на объект одного класса в переменную другого класса, необходимо выполнить преобразование типов — в данном случае от типа Employee к типу Person. И так как Employee наследуется от класса Person, то автоматически выполняется неявное восходящее преобразование — преобразование к типу, которые находятся вверху иерархии классов, то есть к базовому классу.

В итоге переменные employee и person будут указывать на один и тот же объект в памяти, но переменной person будет доступна только та часть, которая представляет функционал типа Person.

Преобразование типов в C#

Подобным образом поизводятся и другие восходящие преобразования:

Person bob = new Client("Bob", "ContosoBank");   // преобразование от Client к Person

Здесь переменная bob, которая представляет тип Person, хранит ссылку на объект Client, поэтому также выполняется восходящее неявное преобразование от производного класса Client к базовому типу Person.

Восходящее неявное преобразование будет происходить и в следующем случае:

object person1 = new Employee("Tom", "Microsoft");  // от Employee к object
object person2 = new Client("Bob", "ContosoBank");  // от Client к object
object person3 = new Person("Sam");                 // от Person к object

Так как тип object — базовый для всех остальных типов, то преобразование к нему будет производиться автоматически.

Нисходящие преобразования. Downcasting

Но кроме восходящих преобразований от производного к базовому типу есть нисходящие преобразования или downcasting — от базового типа к производному. Например, в следующем коде переменная person хранит ссылку на объект Employee:

Employee employee = new Employee("Tom", "Microsoft");
Person person = employee;   // преобразование от Employee к Person

И может возникнуть вопрос, можно ли обратиться к функционалу типа Employee через переменную типа Person. Но автоматически такие преобразования не проходят, ведь не каждый человек (объект Person) является сотрудником предприятия (объектом Employee). И для нисходящего преобразования необходимо применить явное преобразование, указав в скобках тип, к которому нужно выполнить преобразование:

Employee employee1 = new Employee("Tom", "Microsoft");
Person person = employee1;   // преобразование от Employee к Person
 
//Employee employee2 = person;    // так нельзя, нужно явное преобразование
Employee employee2 = (Employee)person;  // преобразование от Person к Employee

Рассмотрим некоторые примеры преобразований:

// Объект Employee также представляет тип object
object obj = new Employee("Bill", "Microsoft");
 
// чтобы обратиться к возможностям типа Employee, приводим объект к типу Employee
Employee employee = (Employee) obj;
 
// объект Client также представляет тип Person
Person person = new Client("Sam", "ContosoBank");
// преобразование от типа Person к Client
Client client = (Client)person;

В первом случае переменной obj присвоена ссылка на объект Employee, поэтому мы можем преобразовать объект obj к любому типу который располагается в иерархии классов между типом object и Employee.

Если нам надо обратиться к каким-то отдельным свойствам или методам объекта, то нам необязательно присваивать преобразованный объект переменной:

// Объект Employee также представляет тип object
object obj = new Employee("Bill", "Microsoft");
 
// преобразование к типу Person для вызова метода Print
((Person)obj).Print();
// либо так
// ((Employee)obj).Print();
 
// преобразование к типу Employee, чтобы получить свойство Company
string company = ((Employee)obj).Company;

В то же время необходимо соблюдать осторожность при подобных преобразованиях. Например, что будет в следующем случае:

// Объект Employee также представляет тип object
object obj = new Employee("Bill", "Microsoft");
 
// преобразование к типу Client, чтобы получить свойство Bank
string bank = ((Client)obj).Bank;

В данном случае мы получим ошибку, так как переменная obj хранит ссылку на объект Employee. Данный объект является также объектом типов object и Person, поэтому мы можем преобразовать его к этим типам. Но к типу Client мы преобразовать не можем.

Другой пример:

Employee employee1 = new Person("Tom"); // ! Ошибка
 
Person person = new Person("Bob");
Employee employee2 = (Employee) person; // ! Ошибка

В данном случае мы пытаемся преобразовать объект типа Person к типу Employee, а объект Person не является объектом Employee. Причем в последнем случае Visual Studio не подскжет, что в данной строке ошибка, и данная строка даже нормально скомилируется, тем не менее в процессе выполнения программы мы получим ощибку. В этом в том числе и кроектся коварство преобразований, поэтому в подобных ситуациях надо проявлять осторожность.

Существует ряд способов, чтобы избежать подобных ошибок преобразования.

Способы преобразований

Во-первых, можно использовать ключевое слово as. С помощью него программа пытается преобразовать выражение к определенному типу, при этом не выбрасывает исключение. В случае неудачного преобразования выражение будет содержать значение null:

Person person = new Person("Tom");
Employee? employee = person as Employee;
if (employee == null)
{
    Console.WriteLine("Преобразование прошло неудачно");
}
else
{
    Console.WriteLine(employee.Company);
}

Стоит отметить, что переменная employee здесь определяется не просто как переменная Employee, а именно Employee? — после названия типа ставится вопросительный знак. Что указывает, что переменная может хранить как значение null, так и значение Employee.

Второй способ заключается в проверке допустимости преобразования с помощью ключевого слова is:

значение is тип

Если значение слева от оператора представляет тип, указаный справа от оператора, то оператор is возвращает true, иначе возвращается false.

Причем оператор is позволяет автоматически преобразовать значение к типу, если это значение представляет данный тип. Например:

Person person = new Person("Tom");
if (person is Employee employee)
{
    Console.WriteLine(employee.Company);
}
else
{
    Console.WriteLine("Преобразование не допустимо");
}

Выражение if (person is Employee employee) проверяет, является ли переменная person объектом типа Employee. И если person является объектом Employee, то автоматически преобразует значение переменной person в тип Employee и преобразованное значение сохраняет в переменную employee. Далее в блоке if мы можем использовать объект employee как значение типа Employee.

Однако, если person не является объектом Employee, как в данном случае, то такая проверка вернет значение false, и преобразование не сработает.

Оператор is также можно применять и без преобразования, просто проверяя на соответствие типу:

Person person = new Person("Tom");
if (person is Employee)
{
    Console.WriteLine("Представляет тип Employee");
}
else
{
    Console.WriteLine("НЕ является объектом типа Employee");
}

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *