Классы и объекты C#: преобразование типов, ключевые слова as и is

На данный момент мы знаем как в 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.

Иерархия классов C#
Иерархия классов

Базовым типом для всех наших классов является 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# сообщит об ошибке преобразования сразу, ещё до запуска программы:

Ошибка CS0266 Не удается неявно преобразовать тип «Person» в «Employee». Существует явное преобразование (возможно, пропущено приведение типов).

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

Unable to cast object of type ‘ConsoleApp3.Person’ to type ‘ConsoleApp3.Employee’.
Это происходит потому что мы пытаемся преобразовать объект типа 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.

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