Проверка типа: is и as
Диспетчер автопарка получил заказ: отвезти груз. В его списке значится только «транспорт номер три». Прежде чем отправить машину, нужно узнать, грузовик ли это: легковая груз не повезёт.
В программе та же задача. Переменная типа Vehicle может вести к грузовику, но вызвать через неё метод грузовика нельзя. Если v объявлена как Vehicle v = new Truck();, строка v.Load(); не скомпилируется:
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
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");
}Давайте разберём по строкам:
v is Truckпроверяет настоящий тип объекта, и строка печатаетTrue: объект — грузовик.- Строка с
v is Vehicleтоже печатаетTrue: грузовик — разновидность транспорта, аisучитывает и базовые классы. - Строка с
v is CarпечатаетFalse: грузовик не легковая. v is Truck tделает ту же проверку и, если она прошла, кладёт ссылку на объект в новую переменнуюtтипаTruck. Внутриifчерезtможно вызватьLoad.
Если переменная равна null, is даёт false для любого типа: объекта нет, и проверять нечего.
Оператор as
as пытается привести ссылку к типу. Если объект подходит, результат — ссылка нужного типа. Если не подходит, результат — null, и никакого исключения нет:
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
}Этот фрагмент продолжает первую программу урока:
w as Truckдаётnull:wведёт к легковой.v as Truckдаёт ссылку на грузовик. После проверки наnullчерез неё вызываютLoad.
Раз as может вернуть null, переменную для его результата объявляют с типом, допускающим null (здесь Truck?), и перед использованием проверяют.
as работает только с типами, которые допускают null. В переменную типа int нельзя положить null, и as нечем сообщить «не получилось». Поэтому при object o = 5; строка int n = o as int; не скомпилируется:
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 t | true, ссылка в t | false |
v as Truck | ссылка типа Truck | null |
is с переменной подходит, когда за проверкой сразу идёт действие. as подходит, когда дальше нужна ссылка или null. Приведение (Truck)v пишут, только когда тип известен наверняка.
Частая ошибка
Привели тип, не проверив его:
Vehicle other = new Car();
Truck bad = (Truck)other; // ✗
bad.Load();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.