Lekce 1 - Úvod do kolekcí a genericita
V dnešní lekci si probereme úvodní teorii ke kolekcím v C# .NET, které si v tomto kurzu podrobněji rozebereme.
Kolekce
Pojem kolekce označuje soubor dat, která jsou většinou stejného typu a
která slouží ke specifickému účelu. Během seriálu jsme se již setkali
se dvěma typy kolekcí: bylo to pole a List
. Kolekcí existuje
velké množství, a ačkoli se zvenku mnohdy tváří podobně, uvnitř
fungují velmi odlišně. Proto si je vybíráme podle konkrétního účelu.
.NET disponuje velkým množstvím předpřipravených kolekcí, s nimiž se v
této sekci postupně seznámíme a s nimiž si zkusíme pracovat.
Generické a obecné kolekce
Když se zamyslíme nad tím, jak si vytvořit vlastní kolekci, jistě po
nějaké době narazíme na problém. Je jím datový typ kolekce. Pokud bychom
si chtěli naprogramovat např. vlastní List
, vytvořili bychom
třídu MujList.cs
, do ní bychom přidali příslušné metody a
vše potřebné. Protože však chceme, aby byla naše kolekce univerzální a
uměla tedy ukládat např. jak datové typy int
, tak uživatele,
nastane problém s datovým typem prvků uvnitř kolekce. Existují dvě
varianty, jak tento problém vyřešit, a i samotný .NET obsahuje kolekce
těchto dvou typů.
Obecné kolekce
Jelikož víme, že všechny datové typy mají jako předka třídu
Object
, můžeme prvky v naší kolekci ukládat právě do tohoto
datového typu. Do kolekce nyní můžeme uložit v podstatě cokoli. Nevýhodou
je, že sama kolekce skutečný datový typ prvků nezná, a proto umí prvky
navracet jen jako obecné objekty. Po získání prvku z kolekce si jej tedy
musíme přetypovat.
Uveďme si příklad negenerické kolekce, kterou je
ArrayList
:
using System.Collections;
{CSHARP_CONSOLE}
ArrayList list = new ArrayList();
list.Add("položka");
string polozka = (string)list[0];
Console.WriteLine(polozka);
{/CSHARP_CONSOLE}
Výstup programu:
Konzolová aplikace
položka
Po vytvoření listu si do něj přidáme položku typu string
.
Abychom tuto položku mohli z listu získat zpět, je třeba ji na
string
zpětně přetypovat.
Pro funkčnost kódu musíme přidat using System.Collections;
.
Fakt, že tento jmenný prostor není ve výchozím projektu přítomen, nám
napovídá, že negenerické kolekce nebudou tou obvyklou volbou.
Generické kolekce
Generické kolekce řeší problém s datovým typem na úrovni jazyka C#.
Zavádí tzv. genericitu. Zjednodušeně řečeno se jedná o možnost
specifikovat datový typ až ve chvíli vytvoření instance. Ve třídě
samotné kolekce se poté pracuje s generickým typem, který slouží jako
zástupce budoucích datových typů. Princip si můžeme představit tak, že
se generický typ ve třídě změní např. na string
ve chvíli,
kdy vytvoříme její instanci. Jedná se tedy o možnost třídy nějakým
způsobem parametrizovat.
Generický List
již známe. Onen datový typ (parametr) se
generickým třídám specifikuje ve špičatých závorkách. Možnost
specifikovat datový typ máme pouze jednou, a to při vytvoření kolekce.
Jakékoli další přetypování odpadá:
{CSHARP_CONSOLE}
List<string> list = new List<string>();
list.Add("položka");
string polozka = list[0];
Console.WriteLine(polozka);
{/CSHARP_CONSOLE}
Výstup programu:
Konzolová aplikace
položka
Program funguje úplně stejně jako ten s negenerickou kolekcí
ArrayList
, nicméně číst můžeme bez nepohodlného
přetypování.
Generické kolekce ty obecné, které se již příliš nepoužívají, nahradily. V kurzu se proto budeme věnovat generickým kolekcím a jejich negenerické verze pouze zmíníme.
Genericita
Genericita je samozřejmě vlastností jazyka C#, tudíž ji máme možnost ve svých třídách používat.
Zatím se nebudeme zatěžovat tvorbou vlastní kolekce. Vytvořme si
třídu, která bude jednoduše spravovat jednu proměnnou. Proměnná bude
generická, tedy libovolného datového typu. Založme si nový projekt, a to
konzolovou aplikaci s názvem Genericita
. Přidejme si novou
třídu, pojmenujme ji nyní pro studijní účely pouze Trida
. V
její deklaraci přidáme generický parametr, který nazveme
T
:
public class Trida<T> { }
Generických parametrů můžeme zadat ve špičatých závorkách více, oddělíme je čárkou. Někdy se to může hodit, my se s tím setkáme dále u generických slovníků.
Přesuneme se do metody Main()
, kde si vytvoříme instanci
naší třídy:
Trida<int> instance = new Trida<int>();
Nezapomeneme na špičaté závorky jak u datového typu, tak u konstruktoru.
Nyní jsme parametru T
v této instanci třídy určili datový typ
int
. Stejně tak si můžeme vytvořit další instanci téže
třídy a parametru T
určit úplně jiný datový typ, např.
string
. Stačí nám tedy jedna třída pro více datových
typů.
Pokračujme a vytvořme si ve třídě atribut. T
můžeme
použít jako běžný datový typ:
private T promenna;
Třídě ještě dodáme konstruktor, který proměnnou inicializuje:
public Trida(T promenna) { this.promenna = promenna; }
V Main()
aktualizujeme vytvoření instance:
Trida<int> instance = new Trida<int>(10);
Nyní instance obsahuje atribut promenna
, který je typu
int
a nabývá hodnoty 10
.
Můžeme dokonce přidat metodu, která bude mít navíc další generický parametr (jiný, než má třída). Mohla by vypadat např. takto:
public bool Porovnej<T2>(T2 a) { return promenna.Equals(a); }
Zkusíme si tedy porovnat náš int
s nějakým jiným
typem:
{CSHARP_CONSOLE} Trida<int> instance = new Trida<int>(10); bool stejne = instance.Porovnej<string>("15"); Console.WriteLine(stejne); {/CSHARP_CONSOLE}
{CSHARP_OOP} class Trida<T> { private T promenna; public Trida(T promenna) { this.promenna = promenna; } public bool Porovnej<T2>(T2 a) { return promenna.Equals(a); } } {/CSHARP_OOP}
Výstup programu:
False
Další konstrukce
Pro úplnost si ještě uveďme několik konstrukcí.
Generický parametr třídy je možné blíže specifikovat, přesněji
omezit. Slouží k tomu klíčové slovo where
. Můžeme tak
nastavit, že udaný datový typ musí např. obsahovat rozhraní
IComparable
:
public class Trida<T> where T: IComparable { ... }
Díky tomu nyní můžeme na proměnných typu T
uvnitř třídy
volat metody z daného rozhraní. Samotné rozhraní může opět obsahovat
generický parametr, abychom generické typy mohli používat i v hlavičkách
jeho metod.
Když uvedeme za where
ještě new()
, můžeme typ
T
uvnitř instanciovat. Taková malá továrna na instance
libovolného typu by mohla vypadat takto:
public class Tovarna<T> where T : new() { public T VytvorInstanci() { return new T(); } }
Pokud ve where
již máme nějaká rozhraní, new()
dáme jednoduše mezi ně a oddělíme jej čárkou.
Nakonec si ukažme, jak můžeme typ parametru omezit z hlediska dědičnosti:
public class Trida<A, B, C> where A : B where B : C { }
Výše jsme deklarovali třídu se třemi generickými parametry, kde
A
je potomkem B
a B
je potomkem
C
.
V příští lekci, Seznam (list) pomocí pole v C#, se podíváme na listy, představíme si různé implementace této kolekce a jejich výhody a nevýhody.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 760x (21.23 kB)
Aplikace je včetně zdrojových kódů v jazyce C#