Typy generyczne
Typy generyczne
Typy generyczne umożliwiają zbudowanie klas, które działają na jeszcze nie zdefiniowanym typie. Dzięki temu możemy przygotować klasy, których metody operują na specyficznym typie i umożliwiają statyczne sprawdzenia poprawności typów, czego nie można by osiągnąć operując na typie Object.
Typów generycznych często używamy w postaci list. W takich obiektach właśnie widzimy potęgę typów generycznych. Gdyby lista korzystała z typu object zamiast konkretnych typów moglibyśmy do listy dodać dowolny często nie odpowiedni obiekt.
W przypadku typów generycznych w czasie kompilacji każde wykorzystanie typu generycznego tworzy odpowiednią implementację z każdym konkretnym typem, na którym taki typ operuje.
Deklaracja typu generycznego
Przy deklaracji klasy wszystkie jej elementy muszą mieć sprecyzowany typ, aby móc go nie określać przy deklaracji. Konieczne jest nadanie mu jakiegoś aliasu, który to w deklaracji klasy będzie wykorzystywany zamiast typu. Alias ten jest zapisywany w nawiasach trójkątnych <>.
1 2 3 4 5 |
public class DataWithErrorDTO<T> { public T StoredObject {get; set;} public ErrorData Error {get; set;} } |
Wykorzystanie
Wykorzystanie klasy typu generycznego jest tak samo proste jak wykorzystanie listy, która właśnie takim typem jest, a więc przy deklaracji zmiennej specyfikujemy dla jakiego typy jest ta zmienna.
1 |
DataWithErrorDTO<int> data = new DataWithErrorDTO<int>(); |
Ograniczenia
Różnorodność typów obsługiwanych przez nasz typ generyczny może być ograniczona. W tym celu po deklaracji naszej klasy wykorzystujemy słowo kluczowe where aliasTypu :
- class – typ musi być typem referencyjnym
- struct – typ musi być typem wartości
- new() – typ musi posiadać publiczny konstruktor bez parametrów. To ograniczenie musi być stosowane na samym końcu listy ograniczeń
- nazwaTypuBazowego lub NazwaInterfejsu – parametr musi dziedziczyć po klasie bazowej lub implementować interfejs
Przykładowa implementacja może wyglądać tak
1 2 3 4 5 |
public class DataWithErrorDTO<T> where T: class { public T StoredObject {get; set;} public ErrorData Error {get; set;} } |
Typy generyczne w C# nie są szablonami tak jak to jest w C++. W czasie kompilacji C# do IL są po prostu reprezentowane jako kod IL który posiada koncepcje typu generycznego. Dopiero w czasie wykonania konkretne typy końcowe są tworzone – nie w czasie kompilacji.
Rozważ niniejszy program:
using System;
class A{}
class B : IB { public void Write(){ Console.WriteLine(typeof(T).FullName); } }
interface IB { void Write(); }
class Program{
static void Main(){
var instance = (IB)Activator.CreateInstance(typeof(B).MakeGenericType(typeof(A)));
instance.Write(); // wypisuje A na konsole
}
}
Nigdzie w czasie kompilacji nie występuje B a jednak instancja się tworzy.
https://blogs.msdn.microsoft.com/carlos/2009/11/09/net-generics-and-code-bloat-or-its-lack-thereof/
https://stackoverflow.com/a/31876747/1495595
Dziękuje za odpowiedź, możliwe że wykorzystałem zbytnie uproszczenie myślowe przy opisie.
Pozdrawiam