Перейти к содержимому
Теория · Наследование и интерфейсы · урок 9 из 10

Проверка типа: is и as

Диспетчер автопарка получил заказ: отвезти груз. В его списке значится только «транспорт номер три». Прежде чем отправить машину, нужно узнать, грузовик ли это: легковая груз не повезёт.

В программе та же задача. Переменная типа Vehicle может вести к грузовику, но вызвать через неё метод грузовика нельзя. Если v объявлена как Vehicle v = new Truck();, строка v.Load(); не скомпилируется:

TEXT
error CS1061: 'Vehicle' does not contain a definition for 'Load' and no accessible extension method 'Load' accepting a first argument of type 'Vehicle' could be found (are you missing a using directive or an assembly reference?)

Компилятор знает только тип переменной, а в Vehicle метода Load нет. Чтобы до него добраться, нужна переменная типа Truck. Её можно получить приведением типа, как числа приводили через (int): Truck t = (Truck)v;. Но если объект окажется не грузовиком, программа остановится с исключением. Поэтому тип сначала проверяют.

Оператор is

C#
Vehicle v = new Truck();
Console.WriteLine(v is Truck);   // True
Console.WriteLine(v is Vehicle); // True
Console.WriteLine(v is Car);     // False
if (v is Truck t)
{
    t.Load(); // loading
}

class Vehicle { }
class Car : Vehicle { }
class Truck : Vehicle
{
    public void Load() => Console.WriteLine("loading");
}

Давайте разберём по строкам:

  1. v is Truck проверяет настоящий тип объекта, и строка печатает True: объект — грузовик.
  2. Строка с v is Vehicle тоже печатает True: грузовик — разновидность транспорта, а is учитывает и базовые классы.
  3. Строка с v is Car печатает False: грузовик не легковая.
  4. v is Truck t делает ту же проверку и, если она прошла, кладёт ссылку на объект в новую переменную t типа Truck. Внутри if через t можно вызвать Load.

Если переменная равна null, is даёт false для любого типа: объекта нет, и проверять нечего.

Оператор as

as пытается привести ссылку к типу. Если объект подходит, результат — ссылка нужного типа. Если не подходит, результат — null, и никакого исключения нет:

C#
Vehicle w = new Car();
Truck? none = w as Truck;
Console.WriteLine(none == null); // True
Truck? found = v as Truck;
if (found != null)
{
    found.Load(); // loading
}

Этот фрагмент продолжает первую программу урока:

  1. w as Truck даёт null: w ведёт к легковой.
  2. v as Truck даёт ссылку на грузовик. После проверки на null через неё вызывают Load.

Раз as может вернуть null, переменную для его результата объявляют с типом, допускающим null (здесь Truck?), и перед использованием проверяют.

as работает только с типами, которые допускают null. В переменную типа int нельзя положить null, и as нечем сообщить «не получилось». Поэтому при object o = 5; строка int n = o as int; не скомпилируется:

TEXT
error CS0077: The as operator must be used with a reference type or nullable type ('int' is a non-nullable value type)

С типом int? та же проверка компилируется: int? n = o as int?;.

Здесь аналогия с диспетчером неточна. Диспетчер может ошибиться, глядя на машину издалека. Проверка is не ошибается: настоящий тип хранится в самом объекте, и среда выполнения читает его оттуда.

Приведение, is или as

На собеседовании часто просят сравнить три способа добраться до грузовика через переменную v типа Vehicle:

КодОбъект — TruckОбъект другого типа
(Truck)vссылка типа Truckисключение InvalidCastException
v is Truck ttrue, ссылка в tfalse
v as Truckссылка типа Trucknull

is с переменной подходит, когда за проверкой сразу идёт действие. as подходит, когда дальше нужна ссылка или null. Приведение (Truck)v пишут, только когда тип известен наверняка.

Частая ошибка

Привели тип, не проверив его:

C#
Vehicle other = new Car();
Truck bad = (Truck)other; // ✗
bad.Load();
TEXT
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'Car' to type 'Truck'.

Программа компилируется: компилятор не знает, к какому объекту поведёт other при запуске. На строке с приведением программа останавливается с исключением InvalidCastException, и Load не выполняется. Проверьте тип через is или as, прежде чем вызывать методы грузовика.

Главное

  • v is Truck проверяет настоящий тип объекта с учётом базовых классов, а v is Truck t ещё и даёт переменную нужного типа.
  • v as Truck возвращает ссылку или null; с int он не компилируется, а с int? работает.
  • Приведение (Truck)v к неподходящему типу останавливает программу исключением InvalidCastException.
  • Для переменной, равной null, проверка is даёт false.