Przejdź do głównej zawartości

[C#] Co to są typy anonimowe?


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ć?

Cóż, skoro wygenerowany typ anonimowy dziedziczy z object, zawsze możemy przekazać person jako object... tyle że niewiele nam z tego przyjdzie, bo nawet gdybyśmy chcieli wewnątrz funkcji rzutować object na konkretny typ... nie wiedzielibyśmy na jaki. Bo przecież jest on anonimowy.

 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