Ранее было рассмотрено нисходящее преобразование типов, которое можно свести к трем вариантам: проверка с помощью ключевого слова is, приведение через оператор as и отлов исключения при прямом преобразовании.
Например, пусть у нас есть следующие классы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Employee { public virtual void Work() { Console.WriteLine("Да работаю я, работаю"); } } class Manager: Employee { public override void Work() { Console.WriteLine("Отлично, работаем дальше"); } public bool IsOnVacation { get; set; } } |
В этом случае преобразование от типа Employee к Manager могло бы выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | static void UseEmployee1(Employee emp) { Manager manager = emp as Manager; if (manager!= null && manager.IsOnVacation==false) { manager.Work(); } else { Console.WriteLine("Преобразование прошло неудачно"); } } static void UseEmployee2(Employee emp) { try { Manager manager = (Manager)emp; if (!manager.IsOnVacation) manager.Work(); } catch (InvalidCastException ex) { Console.WriteLine(ex.Message); } } static void UseEmployee3(Employee emp) { if (emp is Manager) { Manager manager = (Manager)emp; if(!manager.IsOnVacation) manager.Work(); } else { Console.WriteLine("Преобразование не допустимо"); } } |
Все эти методы фактически выполняют одно и то же действие: если переданный в метод объект Employee является объектом Manager и его свойство IsOnVacation равно false, то выполняем его метод Work.
|
|
Несмотря на то, что любой из трех случаев довольно прост в использовании, однако функциональность pattern matching позволяет нам сократить объем кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static void Main(string[] args) { Employee emp = new Manager(); //Employee(); UseEmployee(emp); Console.Read(); } static void UseEmployee(Employee emp) { if (emp is Manager manager && manager.IsOnVacation==false) { manager.Work(); } else { Console.WriteLine("Преобразование не допустимо"); } } |
Pattern matching фактически выполняет сопоставление с некоторым шаблоном. Здесь выполняется сопоставление с типом Manager. То есть в данном случае речь идет о type pattern - в качестве шаблона выступает тип Manager. Если сопоставление прошло успешно, в переменной manager оказывается объект emp. И далее мы можем вызвать у него методы и свойства.
Также мы можем использовать constant pattern - сопоставление с некоторой константой. Например, можно проверить, имеет ли ссылка значение:
1 2 3 4 | if (!(emp is null)) { emp.Work(); } |
Кроме выражения if pattern matching может применяться в конструкции switch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static void UseEmployee(Employee emp) { switch (emp) { case Manager manager: manager.Work(); break; case null: Console.WriteLine("Объект не задан"); break; default: Console.WriteLine("Объект не менеджер"); break; } } |
С помощью выражения when можно вводить дополнительные условия в конструкцию case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static void UseEmployee(Employee emp) { switch (emp) { case Manager manager when manager.IsOnVacation==false: manager.Work(); break; case null: Console.WriteLine("Объект не задан"); break; default: Console.WriteLine("Объект не менеджер"); break; } } |
В этом случае опять же преобразуем объект emp в объект типа Manager и в случае удачного преобразования смотрим на значение свойства IsOnVacation: если оно равно false, то выполняется данный блок case.
|
|
Лекция 20. Деконструкторы
Деконструкторы (не путать с деструкторами) позволяют выполнить декомпозицию объекта на отдельные части. Деконструкторы доступны, начиная с версии C# 7.0 (Visual Studio 2017).
Например, пусть у нас есть следующий класс Person:
1 2 3 4 5 6 7 8 9 10 11 | class Person { public string Name { get; set; } public int Age { get; set; } public void Deconstruct(out string name, out int age) { name = this.Name; age = this.Age; } } |
В этом случае мы могли бы выполнить декомпозицию объекта Person так:
1 2 3 4 5 6 | Person person = new Person { Name = "Tom", Age = 33 }; (string name, int age) = person; Console.WriteLine(name); // Tom Console.WriteLine(age); // 33 |
По сути деконструкторы это не более,чем синтаксический сахар. Это все равно, что если бы мы написали в предыдущих версиях C# следующий набор выражений:
1 2 3 4 | Person person = new Person { Name = "Tom", Age = 33 }; string name; int age; person.Deconstruct(out name, out age); |
При использовании деконструкторов следует учитывать, что метод Deconstruct должен принимать как минимум два выходных параметра. То есть следующее определение метода работать не будет:
1 2 3 4 | public void Deconstruct(out string name) { name = this.Name; } |