W tym wpisie poruszę 2 tematy: czym są metody generyczne oraz czym są klasy generyczne.
Zanim jednak wyjaśnimy sobie te 2 zagadnienia, trzeba powiedzieć czym jest określenie generyczne.
Generyczny, czyli uogólniony. Programowanie generyczne (programowanie uogólnione) jest jednym z paradygmatów programowania. Mówiąc prosto, jest to pisanie kodu uogólnionego, w którym nie do końca wiemy jaki typ danych będzie dostępny, ale wiemy, że niezależnie od typu danych zachowanie będzie takie samo.
Jak zawsze przykład rozjaśni o co chodzi 🙂
Weźmy sobie za przykład pojazd drogowy np. samochód, motocykl, hulajnogę elektryczną. Wszystkie z nich posiadają jakąś swoją prędkość maksymalną. Załóżmy, że chcemy napisać kawałek kodu, który zwróci najszybszy model samochodu z dostępnych na rynku. Chcemy też zwrócić najszybszy motocykl oraz najszybszą hulajnogę elektryczną.
Pokażę to teraz w kodzie.
public abstract class Pojazd
{
public string NazwaPojazdu { get; set; }
public int Vmax { get; set; }
}
public class Samochod : Pojazd
{
}
public class Motocykl : Pojazd
{
}
public class HulajnogaElektryczna : Pojazd
{
}
List<Samochod> samochody = new List<Samochod>()
{
new Samochod(){ NazwaPojazdu = "Renault Twingo", Vmax = 180 },
new Samochod(){ NazwaPojazdu = "VW Passat", Vmax = 230 },
new Samochod(){ NazwaPojazdu = "Fiat 500", Vmax = 170 },
new Samochod(){ NazwaPojazdu = "Skoda Octavia", Vmax = 300 },
};
List<Motocykl> motocykle = new List<Motocykl>()
{
new Motocykl(){ NazwaPojazdu = "Yamaha", Vmax = 290 },
new Motocykl(){ NazwaPojazdu = "Honda", Vmax = 265 },
new Motocykl(){ NazwaPojazdu = "Harley-Davidson", Vmax = 240 },
};
List<HulajnogaElektryczna> hulajnogiElektryczne = new List<HulajnogaElektryczna>()
{
new HulajnogaElektryczna(){ NazwaPojazdu = "DUCATI Scrambler", Vmax = 44 },
new HulajnogaElektryczna(){ NazwaPojazdu = "XIAOMI", Vmax = 36 },
new HulajnogaElektryczna(){ NazwaPojazdu = "LAMBORGHINI", Vmax = 23 },
};
Przejdźmy teraz do sytuacji, w której chcemy wyłonić najszybszy pojazd. Można to zrealizować za pomocą osobnych metod dedykowanych dla każdego typu pojazdu jak poniżej.
public class PorownaniePojazdow
{
public Samochod NajszybszyPojazd(List<Samochod> samochody)
{
return samochody.OrderByDescending(samochod => samochod.Vmax).First();
}
public Motocykl NajszybszyPojazd(List<Motocykl> motocykle)
{
return motocykle.OrderByDescending(motocykl => motocykl.Vmax).First();
}
public HulajnogaElektryczna NajszybszyPojazd(List<HulajnogaElektryczna> hulajnogiElektryczne)
{
return hulajnogiElektryczne.OrderByDescending(hulajnogaElektryczna => hulajnogaElektryczna.Vmax).First();
}
}
Jak widać nasz kod jest podobny. Różni się tak naprawdę typem zmiennej, jaki jest wymagany przez metody oraz typem zwracanym. Tutaj wchodzi pojęcie metod generycznych 🙂
public class PorownaniePojazdow<T> where T : Pojazd
{
public T NajszybszyPojazd(List<T> pojazdy)
{
return pojazdy.OrderByDescending(pojazd => pojazd.Vmax).First();
}
}
Jak widać udało się nam zaoszczędzić pisanie kodu oraz co bardzo ważne nie powtarzamy go 🙂
Teraz jeszcze kod, który pokaże jak możemy wyszukać najszybszy samochód za pomocą obu podejść.
List<Samochod> samochody = new List<Samochod>()
{
new Samochod(){ NazwaPojazdu = "Renault Twingo", Vmax = 180 },
new Samochod(){ NazwaPojazdu = "VW Passat", Vmax = 230 },
new Samochod(){ NazwaPojazdu = "Fiat 500", Vmax = 170 },
new Samochod(){ NazwaPojazdu = "Skoda Octavia", Vmax = 300 },
};
PorownaniePojazdow porownanie1 = new PorownaniePojazdow();
var wynik1 = porownanie1.NajszybszyPojazd(samochody);
PorownaniePojazdow<Samochod> porownanie2 = new PorownaniePojazdow<Samochod>();
var wynik2 = porownanie2.NajszybszyPojazd(samochody);
Skupmy się teraz na klasie i metodzie generycznej.
public class PorownaniePojazdow<T> where T : Pojazd
{
public T NajszybszyPojazd(List<T> pojazdy)
{
return pojazdy.OrderByDescending(pojazd => pojazd.Vmax).First();
}
}
Generyczność jest oznaczana nawiasami “<>”, w których wprowadzamy jakiś typ generyczny. Przyjęło się stosować literę T od słowa Type (typ)
Następnie używamy sekcji “where T : ” i podajemy jakiego typu jest nasze T np. “where T : Pojazd” , “where T : int”, “where T : object”, “where T : string”, “where T : Samochod”.
Wszędzie gdzie będziemy używali naszego typu T w klasie, używamy teraz “<T>”. W procesie uruchomienia programu później, nasz kod będzie miał wstawiany odpowiedni typ w zależności od tego co będzie w kodzie.
Pewnie się jeszcze zastanawiasz dlaczego w metodzie generycznej nasz T jest pojazdem, a w kodzie powyżej było:
PorownaniePojazdow<Samochod> porownanie2 = new PorownaniePojazdow<Samochod>();
Tutaj wchodzi dziedziczenie, a samochód dziedziczył po klasie pojazd dlatego można też zrobić:
PorownaniePojazdow<Motocykl> porownanie2 = new PorownaniePojazdow<Motocykl>();
Kolejną ważną informacją jest fakt, że możemy podawać więcej niż jeden typ generyczny, np.
public class PorownaniePojazdow<T,T1> where T : Pojazd where T1 : RodzajOpon
{
public T NajszybszyPojazd(List<T> pojazdy, T1 opony)
{
return pojazdy.OrderByDescending(pojazd => pojazd.Vmax).First();
}
}
Wtedy kolejne typy określamy kolejnymi numerami T1, T2 itd.
Oczywiście jest to tylko konwencja nazewnicza, która może być stosowana, ale nie musi. Równie dobrze może to być np. nazwa “generycznyTyp”
public class PorownaniePojazdow<generycznyTyp> where generycznyTyp : Pojazd
{
public generycznyTyp NajszybszyPojazd(List<generycznyTyp> pojazdy)
{
return pojazdy.OrderByDescending(pojazd => pojazd.Vmax).First();
}
}
Na koniec jeszcze ostatnia uwaga co do konwencji nazewniczej. Warto stosować nazewnictwo:
- T dla Typów
- E dla elementów (np. List<E>)
- K dla kluczy np. Map<K,V>
- V dla wartości Map<K,V>
Ok, to tyle w temacie metod i klas generycznych. Temat nie jest wcale jakoś mocno skomplikowany, a bardzo ułatwia pisanie kodu i ogranicza czas poświęcony na pisanie kodu 🙂