Typ anonimowy, jak sama nazwa wskazuje, jest typem nieposiadającym nazwy.
Przyjrzyjmy się, jak to działa:
var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" };
W tym przykładzie zmienna person jest typu anonimowego, zawierającego trzy pola. Typy tych pól są ustalane przez kompilator na podstawie ich wartości - w tym przypadku to string, int, string.
Ważne: pola typów anonimowych są readonly!
var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" }; person.Name = "Zdzisław"; //błąd kompilacji
W momencie kompilacji kodu do Common Intermediate Language, generowana jest klasa dla każdego typu anonimowego. Możemy ją podejrzeć w ten sposób:
var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" }; Console.WriteLine(person.GetType().ToString());
Zobaczmy, jaki typ wygenerował się dla obiektu person:
<>f__AnonymousType0`3[System.String,System.Int32,System.String]
Nie wygląda to zbyt atrakcyjnie, ale widzimy przynajmniej, że faktycznie wygenerowały się pola takich typów, jakich się spodziewaliśmy- string, int, string.
Wygenerowany typ:
- ma pola, które są readonly
- dziedziczy z object
- jest sealed - czyli nie można z niego dziedziczyć
Wyobraźmy sobie, że chcemy przekazać obiekt person do funkcji. Jak możemy to zrobić?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace CSharpPractices { class Program { static void Main() { var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" }; ProcessPerson(person); } private static void ProcessPerson(object personObject) { var person = personObject as ???; //i co teraz? } } } |
Na pomoc przychodzi nam słowo kluczowe dynamic, o którym więcej poczytać można tutaj [under construction]. Dla parametru przekazanego do funkcji jako dynamic nie jest sprawdzany typ - przynajmniej nie na etapie kompilacji. Oczywiście użycie nieistniejącego property z obiektu zaowocuje rzuceniem wyjątku RuntimeBinderException.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System; namespace CSharpPractices { class Program { static void Main() { var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" }; ProcessPerson(person); } private static void ProcessPerson(dynamic person) { Console.WriteLine(person.Name); Console.ReadKey(); } } } |
Program ten działa poprawnie i wypisuje na ekran:
Alojzy
Oczywiście, jak zawsze w przypadku dynamic, musimy uważać na pomyłki. Kompilator nie sprawdzi za nas, czy dane property faktycznie istnieje w używanym typie. Jeśli się pomylimy, w runtime zaskoczy nas RuntimeBinderException:
using System; namespace CSharpPractices { class Program { static void Main() { var person = new { Name = "Alojzy", Age = 65, City = "Kozia Wólka" }; ProcessPerson(person); } private static void ProcessPerson(dynamic person) { Console.WriteLine(person.LastName); //brak takiego property w typie anonimowym Console.ReadKey(); } } }
Użycie dynamic dla typów anonimowych jest więc ryzykowne i powinno być unikane.
Gdzie więc używamy typów anonimowych?
Okazuje się, że są one bardzo przydatne, gdy korzystamy z LINQ. Wyobraźmy sobie, że mamy listę osób. Klasa Person ma pola: Name, LastName, Age oraz City.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Person { public Person(string name, string lastName, int age, string city) { Name = name; LastName = lastName; Age = age; City = city; } public override string ToString() { return $"{Name} {LastName}, lat {Age}, z miejscowości {City}"; } public string Name { get; set; } public string LastName { get; set; } public int Age { get; set; } public string City { get; set; } } |
Wyobraźmy sobie, że chcemy pogrupować te osoby w ten sposób, by w każdej grupie znalazły się osoby o tym samym imieniu i pochodzące z tego samego miasta. Możemy to zrobić bardzo łatwo przy użyciu LINQ i typu anonimowego:
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 | using System.Collections.Generic; using System.Linq; using System; namespace CSharpPractices { class Program { static void Main() { var people = new List<Person> { new Person("Alojzy", "Kowalski", 65, "Kozia Górka"), new Person("Alojzy", "Nowak", 55, "Kozia Górka"), new Person("Alojzy", "Kowalski", 23, "Kozia Wola"), new Person("Bernard", "Nowak", 34, "Kozia Górka"), new Person("Bernard", "Kowalski", 11, "Kozia Górka"), }; var peopleGroupedByNameAndCity = people.GroupBy(p => new { p.Name, p.City }); foreach(var peopleGroup in peopleGroupedByNameAndCity) { Console.WriteLine("Klucz grupy to: " + peopleGroup.Key); foreach(var person in peopleGroup) { Console.WriteLine(person); } Console.WriteLine(); } Console.ReadKey(); } } } |
Wynikiem działania tego programu będzie:
Klucz grupy to: { Name = Alojzy, City = Kozia Górka } Alojzy Kowalski, lat 65, z miejscowosci Kozia Górka Alojzy Nowak, lat 55, z miejscowosci Kozia Górka Klucz grupy to: { Name = Alojzy, City = Kozia Wola } Alojzy Kowalski, lat 23, z miejscowosci Kozia Wola Klucz grupy to: { Name = Bernard, City = Kozia Górka } Bernard Nowak, lat 34, z miejscowosci Kozia Górka Bernard Kowalski, lat 11, z miejscowosci Kozia Górka
Gdyby nie użycie typu anonimowego, musielibyśmy stworzyć dość sztuczny typ, zawierający w sobie tylko imię osoby i miasto jej pochodzenia. Taki typ zaśmiecałby nam namespace, gdyż prawdopodobnie nie byłby używany nigdzie indziej. Zapis za pomocą typu anonimowego jest prostszy i mniej konfundujący. Nie musimy też wymyślać nazwy na typ tymczasowy (bo jaka by ona miała być? PersonWithOnlyNameAndLastname?).
Źródła dla tego posta:
- http://www.tutorialsteacher.com/csharp/csharp-anonymous-type
Komentarze
Prześlij komentarz