JavaRush /Курси /C# SELF /Робота з типами: is, ...

Робота з типами: is, as і Pattern Matching

C# SELF
Рівень 34 , Лекція 4
Відкрита

1. Оператор is: перевірка сумісності типів

У реальних застосунках часто трапляється, що у вас є посилання на об’єкт базового класу, але ви знаєте або підозрюєте, що фактичний об’єкт у пам’яті — специфічніший похідний тип. У таких випадках може знадобитися доступ до унікальних членів цього похідного типу. Для безпечної та зручної роботи з такими ситуаціями в C# є кілька ключових інструментів: оператори is, as і, що особливо корисно в сучасних версіях мови, потужні можливості pattern matching (зіставлення зі шаблоном).

is дозволяє перевірити, чи є об’єкт екземпляром указаного типу, його похідного типу або чи реалізує заданий інтерфейс. Він повертає true, якщо об’єкт сумісний із цим типом, і false інакше.

Основне призначення: з’ясувати, чи можна безпечно привести об’єкт до певного типу.

Приклад: перевірка базового типу та наслідування


class Animal { public string Species { get; set; } = "Unknown"; }
class Dog : Animal { public void Bark() => Console.WriteLine("Woof!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("Meow!"); }

Animal myAnimal = new Dog { Species = "Golden Retriever" };

Console.WriteLine($"myAnimal is Animal: {myAnimal is Animal}"); // True
Console.WriteLine($"myAnimal is Dog: {myAnimal is Dog}");       // True
Console.WriteLine($"myAnimal is Cat: {myAnimal is Cat}");       // False

У цьому прикладі myAnimal фактично є Dog. Тому myAnimal is Animal і myAnimal is Dog будуть true.

Приклад: перевірка на null

Оператор is також поводиться передбачувано з null.


Animal nullAnimal = null;
Dog specificDog = new Dog();

Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (null не є екземпляром жодного типу)
Console.WriteLine($"specificDog is null: {specificDog is null}");   // False (об'єкт не null)

Зверніть увагу: null не є екземпляром жодного типу, тому null is MyType завжди false. А someObject is null — це зручний і безпечний спосіб перевірити, чи посилання дорівнює null.

Приклад: використання is з інтерфейсами

Оператор is працює і для перевірки реалізації інтерфейсів.


interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("Flap flap!"); }
class Fish : Animal { }

Animal creature = new Bird();

Console.WriteLine($"creature is Bird: {creature is Bird}");     // True
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // True

creature = new Fish();
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // False

2. Оператор as: безпечне приведення типів

Оператор as призначений для безпечного приведення об’єкта до вказаного типу. На відміну від явного приведення (Type)obj, яке спричинить InvalidCastException у разі невдачі, as повертає null, якщо приведення неможливе. Це ідеально для ситуацій, коли ви не впевнені у фактичному типі об’єкта.

Основне призначення: спробувати привести тип, отримавши null у разі невдачі.

Приклад: базове використання as


class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Square : Shape { public double Side { get; set; } }

Shape myShape = new Circle { Radius = 5.0 };

// Спроба приведення до Circle
Circle circle = myShape as Circle;
if (circle != null) // Обов’язкова перевірка на null!
{
    Console.WriteLine($"Це коло з радіусом: {circle.Radius}"); // Виведення: Це коло з радіусом: 5
}

// Спроба приведення до Square
Square square = myShape as Square;
if (square == null) // Приведення не вдалося, square == null
{
    Console.WriteLine("Це не квадрат."); // Виведення: Це не квадрат.
}

Як видно, as дозволяє уникнути потенційних помилок під час виконання, повертаючи null замість винятку.

Приклад: обмеження as

Важливо пам’ятати, що оператор as працює тільки з посилальними типами і nullable-типами-значеннями. Він не застосовується до звичайних типів-значень, адже вони не можуть приймати значення null.


object someValue = 100;

int num = someValue as int; // ПОМИЛКА КОМПІЛЯЦІЇ: 'as' не можна використовувати з не-nullable типами
int? nullableNum = someValue as int?; // Можна, nullable int
Console.WriteLine($"Nullable int: {nullableNum}"); // Виведення: Nullable int: 100

string str = someValue as string; // Повертає null, оскільки 100 не рядок
Console.WriteLine($"String from int: {str ?? "null"}"); // Виведення: String from int: null

Для не-nullable типів-значень використовуйте або явне приведення ((int)someValue), якщо впевнені у типі (і готові до винятку), або, що краще, pattern matching.

3. Pattern Matching (зіставлення зі шаблоном)

Pattern matching — це потужна й дуже зручна можливість, яка надає елегантніші та безпечніші способи перевірки типів і добування даних з об’єктів. Вона суттєво скорочує повторюваний код і покращує читабельність.

Type pattern (шаблон типу) з is

Це найуживаніша форма pattern matching, яка дозволяє перевірити тип об’єкта і, якщо перевірка успішна, одразу присвоїти його новій змінній цього типу.

Синтаксис: вираз is Тип змінна

Приклад: заміна is + приведення


// Продовжуємо з класами Shape, Circle, Square

Shape currentShape = new Circle { Radius = 7.5 };

// Застарілий, громіздкий спосіб:
if (currentShape is Circle)
{
    Circle c = (Circle)currentShape;
    Console.WriteLine($"Старий спосіб: Коло з радіусом {c.Radius}");
}

// Новий, елегантний спосіб із type pattern
if (currentShape is Circle c) // Перевірка типу і створення змінної 'c'
{
    Console.WriteLine($"Новий спосіб: Коло з радіусом {c.Radius}"); // 'c' вже має тип Circle
}

Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
    Console.WriteLine($"Це квадрат зі стороною {s.Side}");
}

Змінна c (або s) доступна тільки в блоці if, що не дає випадково використати її поза контекстом правильного типу.

Property pattern (шаблон властивості)

Починаючи з C# 8.0, ви можете не тільки перевірити тип, а й одночасно перевірити значення одного чи кількох властивостей об’єкта, а також добути їх у нові змінні.

Синтаксис: вираз is Тип { Властивість1: шаблон_значення, Властивість2: змінна_витягування }

Приклад: перевірка властивості


Shape testShape = new Circle { Color = "Green", Radius = 12.0 };

// Перевіряємо, чи об’єкт — коло і чи його колір зелений
if (testShape is  Circle { Color: "Green" })
{
    Console.WriteLine("Знайдено зелене коло.");
}

// Перевіряємо, чи об’єкт — коло, і одночасно витягуємо його радіус
if (testShape is  Circle { Radius: var r })
{
    Console.WriteLine($"Радіус витягнуто: {r}");
}

// Комбінуємо перевірку і витягування:
if (testShape is  Circle { Color: "Green", Radius: var radiusVal } circleObj)
{
    Console.WriteLine($"Витягнуто зелене коло. Радіус: {radiusVal}, Об’єкт: {circleObj.Radius}");
}

Шаблон властивості суттєво спрощує складні умовні вирази.

Приклад: діапазони та логічні оператори в property patterns

Тепер ви можете перевіряти, що властивості об’єкта задовольняють певній умові:


Circle bigCircle = new Circle { Radius = 25.0, Color = "Blue" };

// Перевіряємо радіус більше 20 І колір синій
if (bigCircle is Circle { Radius: > 20, Color: "Blue" })
{
    Console.WriteLine("Знайдено велике синє коло.");
}

// Перевіряємо радіус у діапазоні (5..15)
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
    Console.WriteLine("Коло середнього розміру.");
}

Важливо! На відміну від типу bool, тут використовуються ключові слова: and, or і not.

4. Switch expressions і switch statements з pattern matching

Найбільшу гнучкість і читабельність pattern matching демонструє в операторах switch. Вони дозволяють виконувати різну логіку залежно від шаблону, що збігся.

Приклад: switch statement

Дозволяє визначати блоки коду для кожного шаблону, який збігся.


// Класи Animal, Dog, Cat як на початку лекції
Animal currentCreature = new Dog { Species = "Poodle" };

switch (currentCreature)
{
    case Dog d: // Шаблон типу: якщо це Dog, присвоїти змінній 'd'
        d.Bark();
        Console.WriteLine($"Це собака породи {d.Species}.");
        break;
    case Cat c when c.Species == "Siamese": // Шаблон типу з умовою (when-clause)
        c.Meow();
        Console.WriteLine($"Це сіамська кішка.");
        break;
    case Animal a: // Якщо це просто Animal (і не Dog/Cat)
        Console.WriteLine($"Це просто тварина виду {a.Species}.");
        break;
    case null: // Шаблон null для обробки null-значень
        Console.WriteLine("Об’єкт дорівнює null.");
        break;
    default: // Якщо жоден із наведених вище шаблонів не збігся
        Console.WriteLine("Невідома істота.");
        break;
}

Порядок кейсів у switch важливий: специфічніші шаблони слід розміщувати перед загальнішими.

Приклад: switch expression

Це компактніший синтаксис для switch, який повертає значення. Ідеально підходить для перетворення об’єкта на інше значення на основі його типу чи властивостей.

Синтаксис: вираз switch { шаблон1 => результат1, шаблон2 => результат2, ... _ => типовий_результат }


Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "Red" };

string shapeInfo = processShape switch
{
    Circle { Radius: var r } when r > 10 => $"Велике коло (R={r})", // Шаблон властивості з умовою
    Circle { Color: "Blue" } c =>  $"Синє коло (R={c.Radius})",   // Шаблон властивості з витягуванням
    Circle c =>  $"Звичайне коло (R={c.Radius})",                     // Простий шаблон типу
    Rectangle { Width: var w, Height: var h } when w == h =>  $"Квадрат ({w}x{h})", // Квадрат
    Rectangle r =>  $"Прямокутник ({r.Width}x{r.Height})",         // Будь-який інший прямокутник
    null =>  "Фігура не існує (null)",                           // Шаблон null
    _ =>  "Невідома фігура"                                     // Дискард-шаблон (_) замість 'default'
};

Console.WriteLine(shapeInfo); // Виведення: Квадрат (5x5)

switch expression дуже зручний для коротких, виразних перетворень.

Var pattern (шаблон var)

Починаючи з C# 7.0, ви можете використовувати var у pattern matching. var завжди збігається з будь-яким об’єктом (крім null) і витягує його у змінну відповідного типу.

Приклад: використання var pattern


object obj = "Hello World";

if (obj is var  result) // Завжди true, result буде string
{
    Console.WriteLine($"Тип: {result.GetType().Name}, Значення: {result}");
}

obj = 123;
string typeName = obj switch
{
    var x when x is int => "Це ціле число",
    var y when y is string =>  "Це рядок",
    _ => "Щось інше"
};
Console.WriteLine(typeName); // Виведення: Це ціле число

Шаблон var рідко використовують сам по собі для перевірки типу, проте він дуже корисний усередині інших шаблонів або switch для витягування значення.

5. is vs as vs pattern matching: коли і що використовувати?

Вибір інструменту залежить від конкретного завдання і версії C#, яку ви використовуєте.

Оператор is (простий):

Застосування: коли треба просто перевірити, чи є об’єкт певного типу, і подальші дії не вимагають негайного приведення чи доступу до специфічних членів.


    if (myObject is SomeType)

Оператор as:

Застосування: коли ви намагаєтеся привести посилальний тип (або nullable тип-значення) і хочете обробити невдачу без винятку, використовуючи перевірку на null.


    MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }

Pattern matching (is Type variable):

Сучасний підхід, який варто використовувати: поєднує перевірку типу і безпечне приведення в одному, більш читабельному рядку. Працює для всіх типів.

Застосування: коли треба перевірити тип і одразу ж використати специфічні члени цього типу.


    if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }

Pattern matching (switch expressions / statements):

Сучасний підхід, який варто використовувати: коли треба виконати різні дії або отримати різні значення залежно від типу об’єкта, його властивостей чи інших характеристик. Це значно чистіше, ніж довгі ланцюжки if-else if.

Застосування: реалізація поліморфної поведінки або складної логіки вибору на основі характеристик об’єкта.


string GetShapeInfo(Shape s) => s switch
{
    Circle c => $"Коло R={c.Radius}",
    Rectangle r => $"Прямокутник W={r.Width} H={r.Height}",
    _ => "Невідомо"
};
1
Опитування
Відкладене виконання, рівень 34, лекція 4
Недоступний
Відкладене виконання
Оптимізація роботи з колекціями
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ