Dzisiaj przyszedł czas na wzorzec agregujący w pewien sposób wszystkie poprzednie, a mianowicie wzorzec rekurencyjny. Załóżmy, że mamy następującą hierarchię klas, np.:
public class Employee { public string Name { get; set; } public object Salary { get; set; } } public class Manager : Employee { public object DirectReports { get; set; } }
W poprzednich wersjach C#, było możliwe jedynie sprawdzenie czy obiekt jest typu “Manager” czy innej implementacji. W wersji 7.0 możemy:
object employee = new Manager() { Salary = 53, DirectReports = new Employee[5] }; if (employee is Manager { Salary is int salary,DirectReports is Employee[] directReports}) { Console.WriteLine("It's a manager with salary {0} and direct reports {1}", salary, directReports.Length); }
Widzimy tutaj wzorzec rekurencyjny składający się z:
- type pattern – sprawdzamy czy obiekt jest typu Manager
- zagnieżdżonego type pattern – sprawdzamy czy Salary jest liczbą całkowitą
- zagnieżdżonego type pattern – sprawdzamy czy DirectReports jest tablicą.
Jeśli któryś z warunków jest nieprawdziwy, całość zwróci false. Jeśli np. zamienimy tablicę na kolekcję, wtedy na ekranie nic się nie wyświetli:
private static void Main(string[] args) { object employee = new Manager() { Salary = 53, DirectReports = new List<Employee>() }; if (employee is Manager { Salary is int salary,DirectReports is Employee[] directReports}) { Console.WriteLine("It's manager with salary {0} and direct reports {1}", salary, directReports.Length); } }
Można również umieścić “constant pattern”, o którym pisałem w poprzednim poście:
object employee = new Manager() { Name="Piotr" }; if (employee is Manager { Name is "Piotr"}) { Console.WriteLine("Name: Piotr"); }
W tym przypadku, instrukcja zwróci true, gdy właściwość Name ma wartość “Piotr”. Constant pattern, nie wyodrębnia zawartości, jedynie sprawdza czy zmienna jest równa danej stałej.
Zmieńmy teraz trochę strukturę klas na:
public class Employee { public string Name { get; set; } } public class Manager : Employee { public object DirectReport { get; set; } }
Za pomocą wzorca możemy rekurencyjnie sprawdzać właściwości o dowolnym zagnieżdżeniu, tzn.:
object employee = new Manager() { DirectReport = new Employee { Name = "Piotr" } }; if (employee is Manager { DirectReport is Employee { Name is string name}}) { Console.WriteLine(name); }
Rekurencyjna jest zatem przerywana, gdy jakiś warunek zwraca false. Jeśli zwraca true, wtedy kolejne warunki będą sprawdzane, przeglądając graf obiektów o dowolnej głębokości. W powyższym przykładzie, zatem musimy mieć obiekt typu Manager, w którym DirectReport jest typu Employee, a właściwość Name jest napisem.
Tak jak zwykle, na zakończenie zobaczmy kod po kompilacji:
private static void Main(string[] args) { object employee = new Manager { Salary = 53, DirectReports = new List<Employee>() }; Manager manager = employee as Manager; int salary; Employee[] directReports; bool arg_65_0; if (manager != null) { int? num = manager.Salary as int?; salary = num.GetValueOrDefault(); if (num.HasValue) { directReports = (manager.DirectReports as Employee[]); arg_65_0 = (directReports != null); goto IL_65; } } arg_65_0 = false; IL_65: bool flag = arg_65_0; if (flag) { Console.WriteLine("It's manager with salary {0} and direct reports {1}", salary, directReports.Length); } }
Wyraźnie widzimy, że wszystkie warunki muszą zostać spełnione tzn.:
- Employee musi być typu Manager
- Salary jest liczbą całkowitą
- DirectReports jest tablicą Employee
Ponadto, jak widzimy z powyższego kodu, typy proste mogą być nullable, o ile mają w sobie jakąś wartość. Zatem salary mogłaby być typu int?, o ile wartość nie jest NULL.
No super. A do czego to służy :-)?
Jakie jest praktyczne zastosowanie?
@rbl
Zwięzłości i czytelności kodu. Wszystko to możesz osiągnąć także if’ami ale traci kod na czytelności.
Też się na tym zastanawiam..:)
Pytanie gdzie taki kod bedzie przydatny. Wydaje mi sie ze ten feature jest dodawany ze zwgledu na integracje tupli w C# i nie powinien byc uzywany poza nimi.