Содержание
На данный момент мы знаем как в C# работает наследование, как создавать потомков класса и их использовать в свих приложениях. Однако, при использовании механизмов наследования в C# (и не только) достаточно часто возникает необходимость преобразования типов объектов или же определить какой тип фактически использовался при создании какого-либо объекта в иерархии. С такой потребностью мы могли столкнуться, например, в теме про полиморфизм. До сих пор мы имели представление о преобразовании базовых типов данных. В этой части мы разберемся с тем как можно производить преобразования пользовательских типов данных.
Иерархия классов C#
Рассмотрим следующую иерархию классов:
class Person { public string Name { get; set; } public Person(string name) { Name = name; } public void Display() { 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
—> Person
—> Employee
|Client
.

Базовым типом для всех наших классов является Object
. У классов Employee
и Client
классом-родителем является класс Person
. На примере этой цепочки наследования мы и рассмотрим преобразования типов в C#.
Восходящее преобразование (upcasting) в C#
Как мы уже знаем из темы про полиморфизм в C#, объекты производного типа (наследники), которые находятся внизу иерархии классов, в то же время представляют собой и базовый тип (родителя).
Например, объект Employee
одновременно является и объектом класса Person
и мы можем написать, например, следующее:
Employee employee = new Employee("Ваня", "Рога и Копыта"); Person person = employee; //преобразование от Employee к Person Console.WriteLine(person.Name);
В приведенном примере переменной person
типа Person
присваивается ссылка на объект типа Employee
. Однако, для того, чтобы сохранить ссылку на объект одного класса в переменную другого класса, необходимо выполнить преобразование типов. В нашем случае от типа Employee
(наследник) к типу Person
(родитель). А так как Employee
наследуется от Person
, то в C# автоматически выполняется неявное восходящее преобразование — преобразование к типу, находящемуся выше в иерархии классов, то есть к базовому классу (родителю). Это и есть upcasting.
В итоге переменные employee
и person
будут указывать на один и тот же объект в памяти, однако переменной person
будет доступна только та часть свойств и методов класса, которая имеется у типа Person
.
Аналогичным образом происходят и другие восходящие преобразования, например:
Person person2 = new Client("Василий", "Мегапроект");//преобразование от Client к Person
Здесь переменная person2
, которая имеет тип 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) в C#
Помимо восходящих преобразований от производного к базовому типу есть нисходящие преобразования или downcasting, то есть преобразование от базового типа к производному. Например, в следующем коде переменная person
хранит ссылку на объект Employee
:
Employee employee = new Employee("Tom", "Microsoft"); Person person = employee; // преобразование от Employee к Person
Может возникнуть вопрос: можно ли обратиться к свойствам и методом типа Employee
через переменную типа Person
. В C# такие преобразования автоматически не проходят. Для нисходящего преобразования необходимо применить явное преобразование, указав в скобках тип, к которому нужно выполнить преобразование (аналогичный пример у нас был в теме про полиморфизм и ещё раньше — в теме про преобразование базовых типов):
Employee employee = new Employee("Tom", "Microsoft"); Person person = employee; // преобразование от Employee к Person //Employee employee2 = person; // так нельзя, нужно явное преобразование Employee employee2 = (Employee)person; // преобразование от Person к Employee
Ниже представлены некоторые примеры нисходящих преобразований в C#:
// Объект Employee также представляет тип object object obj = new Employee("Bill", "Microsoft"); // чтобы обратиться к возможностям типа Employee, приводим объект к типу Employee Employee emp = (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 для вызова метода Display ((Person)obj).Display(); // либо так // ((Employee)obj).Display(); // преобразование к типу Employee, чтобы получить свойство Company string comp = ((Employee)obj).Company;
// Объект Employee также представляет тип object object obj = new Employee("Bill", "Microsoft"); // преобразование к типу Client, чтобы получить свойство Bank string bank = ((Client)obj).Bank;
Переменная obj
хранит ссылку на объект Employee
. Этот объект является также объектом типов object
и Person
, поэтому мы можем преобразовать его к этим типам, однако, к типу Client
мы преобразовать не можем. Ещё один пример неверных преобразований:
Employee emp = new Person("Tom"); // ! Ошибка Person person = new Person("Bob"); Employee emp2 = (Employee) person; // ! Ошибка
При этом, в первом случае, компилятор C# сообщит об ошибке преобразования сразу, ещё до запуска программы:
Во втором же случае, ошибка более серьезная и узнаем мы о ней только при запуске программы, когда увидим следующее сообщение:
Person
к типу Employee
, но объект Person
не является объектом Employee
. Существует несколько способов избежать подобных ошибок преобразования.Способы преобразований C#
Ключевое слово AS
С помощью ключевого слова AS
программа пытается преобразовать выражение к определенному типу и при этом не выдает исключение. В случае неудачного преобразования выражение будет содержать значение null
:
Person person = new Person("Tom"); Employee emp = person as Employee; if (emp == null) { Console.WriteLine("Преобразование прошло неудачно"); } else { Console.WriteLine(emp.Company); }
Исключение InvalidCastException
Второй способ заключается в отлавливании исключения InvalidCastException
, которое возникнет в результате неудачного преобразования:
Person person = new Person("Tom"); try { Employee emp = (Employee)person; Console.WriteLine(emp.Company); } catch (InvalidCastException ex) { Console.WriteLine(ex.Message); }
Про работу с исключениями мы подробнее поговорим в одной из следующих частей учебника.
Проверка доступности преобразования. Ключевое слово is
Третий способ заключается в проверке допустимости преобразования с помощью ключевого слова is
:
Person person = new Person("Tom"); if(person is Employee) { Employee emp = (Employee)person; Console.WriteLine(emp.Company); } else { Console.WriteLine("Преобразование не допустимо"); }
person is Employee
проверяет, является ли переменная person
объектом типа Employee
. Но так как в данном случае не является, то проверка вернет значение false
, и преобразование не сработает. Более короткий пример использования ключевого слова is
:Person person = new Person("Tom"); if (person is Employee emp) { Console.WriteLine(emp.Company); } else { Console.WriteLine("Преобразование не допустимо"); }
В последнем примере мы, используя ключевое слово is
проверяем является ли переменная person типом Employee
и, если является, то автоматически присваиваем переменной emp
значение person
.
Итого
Сегодня мы рассмотрели варианты преобразования типов данных в C#: восходящее преобразование (upcasting
) и нисходящее (downcasting
). Также рассмотрели варианты того, как избежать ошибок преобразования типов в C# и научились использовать ключевые слова as
и is
.