Lekce 1 - Úvod do kolekcí a genericita

C# .NET Kolekce a LINQ Úvod do kolekcí a genericita American English version English version

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V dnešní lekci si řekneme nějakou 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 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ě a vybíráme si je podle konkrétního účelu. .NET disponuje velkým množstvím předpřipravených kolekcí, se kterými se v této sekci postupně seznámíme a zkusíme si s nimi pracovat.

Generické a obecné kolekce

Když se zamyslíme nad tím, jak bychom si udělali vlastní kolekci, jistě bychom po nějaké době dospěli k problému. Byl by jím datový typ kolekce. Chtěli bychom si např. naprogramovat vlastní List, vytvořili bychom třídu MujList.cs, do ní 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 inty, tak uživatele, bude problém s datovým typem prvků uvnitř kolekce. Existují 2 varianty, jak tento problém vyřešit a i samotný .NET obsahuje kolekce těchto 2 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, je jí ArrayList:

ArrayList list = new ArrayList();
list.Add("položka");

string polozka = (string)list[0];

Po vytvoření listu si do něj přidáme položku typu string. Abychom tuto položky 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;. Jelikož tento jmenný prostor není ve výchozím projektu přítomen, napovídá nám to, že negenerické kolekce nebudou ta obvyklá volba.

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 pro budoucí datový typ. Můžeme si to 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 a onen datový typ (parametr) se generickým třídám specifikuje ve špičatých závorkách. Máme možnost specifikovat datový typ pouze jednou, při vytvoření kolekce. Jakékoli další přetypování odpadá:

List<string> list = new List<string>();
list.Add("položka");

string polozka = list[0];

Program funguje úplně stejně, jako ten s negenerickou kolekcí ArrayList, nicméně číst můžeme bez nepohodlného přetypování.

Generické kolekce nahradily kolekce obecné a ty se již příliš nepoužívají. V seriálu se budeme věnovat generickým kolekcím a jejich negenerické verze pouze zmíníme.

Genericita

Genericita je samozřejmě vlastnost jazyka C# a my 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žte si nový projekt, konzolovou aplikaci s názvem Genericita. Přidejte si novou třídu, pojmenujme ji nyní pro studijní účely pouze Trida. V její deklaraci přidáme generický parametr, který pojmenujeme 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 udělat další instanci té samé třídy a parametru T dát úplně jiný datový typ, např. string. Stačí nám tedy 1 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 proměnná, 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:

instance.Porovnej<string>("15");

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 můžeme na proměnných typu T nyní 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 uvnitř typ T 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 máme ve where již nějaká rozhraní, new() dáme jednoduše mezi ně a oddělíme čá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.


 

Stáhnout

Staženo 479x (21.23 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
15 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Miniatura
Všechny články v sekci
Kolekce v C# .NET a LINQ
Miniatura
Následující článek
Seznam (List) pomocí pole v C#
Aktivity (4)

 

 

Komentáře

Avatar
Radek Veverka
Redaktor
Avatar
Radek Veverka:24.7.2015 8:53

Ahoj, jak je to s konstruktorem při instanciování generického typu T? Vůbec se nezavolá? Anebo lze nějak specifikovat, abych mohl použít jen množinu datových typů, které mají v konstruktoru stejné dané parametry?

 
Odpovědět 24.7.2015 8:53
Avatar
Miroslav Mazal:6. září 18:40

Ahoj. Chápu to správně, že když chci v Main odkazovat na proměnné z nějaké třídy, nemohu to udělat přímo, ale musím nejdříve v Main vytvořit Instanci dané třídy?

 
Odpovědět 6. září 18:40
Avatar
Jirka
Člen
Avatar
Odpovídá na Miroslav Mazal
Jirka:6. září 19:08

Ahoj.

V "Main" můžeš přímo jen statické-metody třídy. Obvykle ale chceš používat instanci, kterou si vytvoříš třeba takto:

public class Main {
public static main(...) {
ArrayList<String> alist = new ArrayList<>();
alist.add("string0"); //do alist pridas string0
alist.add("string1"); //do alist pridas string1
alist.add("string2"); //do alist pridas string2
for(int index = 0; index < alist.count(); index ++) {
        System.out.println(alist.get(index));
}
}
}
Odpovědět 6. září 19:08
Kdo nic nedělá, nic nezkazí.
Avatar
Miroslav Mazal:6. září 19:26

Ahoj. Snažím se pochopit funkčnost tohoto kódu.

    public class ListA <T>
    {
            private T[] arr;
            private int count;

            public int Count
            {
                get
                {
                    return this.count;
                }
            }
            private const int INITIAL_CAPACITY = 4;
            public ListA(int capacity = INITIAL_CAPACITY)
            {
                this.arr = new T[capacity];
                this.count = 6;
            }

    }
}

Chápu to tak, že je to třída (List) zatím s nespecifikovaným typem dat. Tato třída obsahuje pole dat s názvem arr a intovou proměnnou count. Pak je zde vlastnost, abychom se mohli odjinud zeptat, jaký je momentálně count, neboli aktuální počet polí. Je to tak? Pak se už trochu ztrácím...

 
Odpovědět 6. září 19:26
Avatar
Miroslav Mazal:6. září 19:27

this.count má být 0. Přepsal jsem to, když jsem zkoušel, co se stane....

 
Odpovědět 6. září 19:27
Avatar
Odpovídá na Miroslav Mazal
Andy Scheuchzer:6. září 20:09

count není počet polí, ale počet prvků v listu, tedy kolik těch prvků má pole navenek. INITIAL_CAPACITY je konstanta určující, kolik prvků má mít pole ihned po vytvoření listu. A konstruktor snad chápeš, ne (to int capacity = INITIAL_CAPACITY znamená, že to tam nemusíš nutně psát a v takovém případě se přiřadí INITIAL_CAPACI­TY)?

@Jirka tady jsme v C#, pokud čtu dobře ;-)

Odpovědět 6. září 20:09
Za správnost neručím.
Avatar
Odpovídá na Andy Scheuchzer
Miroslav Mazal:6. září 20:41

Díky. Mátlo mě to INITIAL... Máš pravdu, pak jsem si to tu našel, že count je počet vložených prvků. Mám sice hlavu plnou informací, ale přechod do skutečného psaní je těžký :-P Často se vracím do prostudovaných kapitol, a často mi až pak různé věci docvaknou. Vrtá mi ještě hlavou, co by se stalo, kdyby tam nebyly ty this? Kde by se to pak projevilo. Protože program funguje i bez toho. (Omlouvám se, že v této fázi mám asi pro programátory hloupé otázky.)

 
Odpovědět 6. září 20:41
Avatar
Odpovídá na Miroslav Mazal
Andy Scheuchzer:6. září 20:48

this prostě zabraňuje kolizi názvů, zatím by se nic nestalo. Ale kdybys měl v tom svém listu metodu obsahující

int count;

bez použití this by ses odkazoval na count své metody, ne celé třídy. A s tím vracením se – já to taky občas dělám a např. delegáty a anonymní metody nechápu (kdybych je musel používat, asi je časem pochopím), ale až na takové úrovni? No, dokud tě odsud někdo nevykáže za spam, asi se klidně ptej…

Odpovědět 6. září 20:48
Za správnost neručím.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 8 zpráv z 8.