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

Последнее обновление: 28.12.2021

В предыдущей главе мы говорили о преобразованиях объектов простых типов. Сейчас затронем тему преобразования объектов классов. Допустим, у нас есть следующая иерархия классов:

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");
}
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850