C# 2.0: Generics

Publikoval Michal Kočí dňa 13.9.2004 o 21:11 v kategórii .Net

Generics je nový rys jazyka C# v jeho verzii 2.0. V kocke povedané, jedná sa o použitie všeobecného dátového typu pri vytváraní tried. Jeho najvačšie použitie predpokladám pri tvorbe rôznych zoznamov (i keď sa môžem mýliť) a teda celý jeho popis zhrniem aj ja na príklade triedy, ktorá bude obsahovať pole prvkov. Prvky budú môcť byť ľubovoľného typu, ale vždy len jedného. Veľkosť pola sa bude musieť určiť pri volaní konštruktoru a bude nemenná.

Poďme sa najskôr pozrieť, ako by sme niečo podobné mohli riešiť v C# verzie 1 a aké sú hlavné nedostatky tohto riešenia. Ak sme doteraz chceli uchovávať pole prvkov, mohli sme použiť jednu z nasledovných variánt:

Typové pole presne stanoveného typu:

String[] poleRetazcov;
ListItem[] polePoloziekZoznamu
Keď však presne stanovíme typ položiek, žiadný iný typ položky do takéhoto pola nedostaneme. Ak chceme v našej triede uchovávať len jeden typ položiek, ale pri tvorbe triedy nevieme presne povedať, ktorý typ to bude, je toto riešenie nevyhovujúce.

Môžeme teda použiť pole objektov

Object[] poleObjektov
Do takéhoto poľa dostaneme prvok ľubovoľného typu. Avšak, pri vkladaní prvkov bude prebiehať boxovanie alebo upcast na typ Object (v závislosti od typu prvku). Teda sa jedná o zníženie výkonnosti. Ďaľším nedostatkom je, že v čase kompilácie nie je ošetrený typ vkladaných prvkov, do poľa jednoducho môžeme vložiť dve celé čísla typu int a jeden reťazec typu string. Ak v poli budeme očakávať iba celé čísla, reťazec nám za behu programu vyvolá vynímku
Object[] poleObjektov = new Object[3];
// vlozime prve dva prvky
poleObjektov[0] = 17;
poleObjektov[1] = "Ahoj svet";
// treti prvok ma byt suctom prvych dvoch
poleObjektov[2] = (int) poleObjektov[0] + (int) poleObjektov[1];
Takže toto riešenie tiež nie je ideálne. Rovnaké problémy nám nastanú aj pri použití kolekcií (napríklad ArrayList)

No a poďme sa pozrieť, ako sa tento problém dá vyriešiť s použiím Genericsov. Pripravíme novú triedu, ktorá bude všeobecná, pri jej použití uvedieme s akým typom dát budeme pracovať (dopredu tento typ budeme označovať ako T). Trieda bude mať konštruktor, ktorý bude preberať celočíselný parameter udávajúci veľkosť pola. V konštruktore sa vytvorí pole typu T. Ďalej budeme mať metódu Set slúžiacu na nastavenie hodnoty i-tému prvku (hodnota bude typu T) a metódú Get slúžiacu na získanie hodnoty i-tého prvku (hodnota bude tiež typu T). Takže výsledná trieda bude vyzarať nasledovne:

class FixedLengthArray
{
    T[] tArray;
    public FixedLengthArray(int capacity)
    {
        tArray = new T[capacity];
    }
    public void Set(int index, T t)
    {
        if ((index >= 0) && (index < tArray.Length))
        {
            tArray[index] = t;
        }
        else
        {
            throw new ArgumentOutOfRangeException();
        }
    }
    public T Get(int index)
    {
        if ((index >= 0) && (index < tArray.Length))
        {
            return tArray[index];
        }
        else
        {
            throw new ArgumentOutOfRangeException();
        }
    }
}

Tak a prichádzame pomaly do finále. Ak teraz budem chcieť použiť túto novú triedu, oznámim jej s akým typom chcem pracovať

// budem pouzivat prvky typu int, pole nech ma desat prvkov
FixedLengthArray poleCisiel = new FixedLengthArray(10);
// budem pouzivat prvky typu string, pole nech ma 5 prvkov
FixedLengthArray poleRetazcov = new FixedLengthArray(5);
// budem pouzivat prvky typu ListItem
FixedLengthArray polePoloziek = new FixedLengthArray(10);
No a kompilátor mi už v čase kompilácie nedovolí zavolať metódu Set s iným typom než som nadefinoval. Teda napríklad:
FixedLengthArray poleCisiel = new FixedLengthArray(10);
poleCisel.Set(0, 135); // bez chyby
poleCisel.Set(1, "Ahoj svet"); // chyba pri kompilacii

Týmto sa odstráni nepríjemné boxovanie/odboxovanie a pretypovávanie, navyše kontrola typov bude prebiehať už pri kompilácii. Jednoducho skvelé. Navyše v namespace System.Collections.Generic je predpripravených viac než dosť Generic tried (napríklad Collection, Dictionary, List, Queue, Stack, ...)

Ďaľšou skvelou vymoženosťou je možnosť nastaviť obmedzenia na typové parametre. Môžme teda jednoducho povedať, že našu triedy môžme vytvoriť len ak typový parameter bude trieda/štruktúra alebo že typový parameter musí byť presného typu (a v prípade triedy typu z neho zdedeného). Používa sa na to klúčové slovo where:

// typovy parameter musi byt trieda ListItem alebo z nej odvodena trieda
class FixedLengthArray where T: ListItem

Toľko jemný popis Genericsov, táto črta bola rozhodne vítaná.

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.