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[] polePoloziekZoznamuKeď 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[] poleObjektovDo 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 FixedLengthArrayNo a kompilátor mi už v čase kompilácie nedovolí zavolať metódu Set s iným typom než som nadefinoval. Teda napríklad: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);
FixedLengthArraypoleCisiel = 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 FixedLengthArraywhere T: ListItem
Toľko jemný popis Genericsov, táto črta bola rozhodne vítaná.
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.