Date post: | 12-Apr-2017 |
Category: |
Education |
Upload: | jan-hridel |
View: | 136 times |
Download: | 0 times |
Pokročilé techniky programováníPřednáška 03 – Genericita
Genericita„Obdoba šablon v jazyce C++“
Deklarace typu, který pracuje s typovým parametrem, se nazývá deklarace generického typu. Jazyk C# poskytuje následující generické typy:
generické třídy,
generické struktury,
generická rozhraní,
generické delegáty.
Problém genericity Mějme datovou strukturu zásobník pracující s prvky typu object.
class Zasobnik {
object[] pole = new object[100]; int pocet;
public void Vloz(object obj) {
if (pocet < pole.Length) pole[pocet++] = obj; } public object Odeber() {
if (pocet > 0) return pole[--pocet]; return null;
} }
Problém genericity Do uvedeného zásobníku lze přidat hodnotu libovolného datového typu bez nutnosti přetypování. Při odebrání prvku se musí provést přetypování:
Zasobnik zasobnik = new Zasobnik(); zasobnik.Vloz(1); zasobnik.Vloz(2); int cislo = (int)zasobnik.Odeber();
Generické třídy a struktury Pro generické struktury platí stejná pravidla jako pro generické třídy.
Syntaxe generické třídy:
deklarace generické třídy:
modifikátorynep partialnep class identifikátor seznam_typových_parametrů předeknep omezení_typových_parametrůnep tělo ;nep
seznam_typových_parametrů:
< typové_parametry >
Generické třídy - příklad Příklad demonstruje deklaraci generického zásobníku a jeho použití pro zásobník celých čísel.
https://goo.gl/9a7zf4
Uvedený zásobník je typově bezpečný, tj. může pracovat pouze s celými čísly. Žádné přetypování při použití metody Odeber není nutné.
Generické třídy Generické typy mohou být „přetíženy“. Lze deklarovat generické typy se stejným jménem lišící se počtem typových parametrů. Lze také deklarovat stejně pojmenovanou negenerickou a generickou třídu. Např. class C { } class C<T> { } // OK class C<T, U> { } // OK struct C<A, B> { } // Chyba – existuje C<T, U>
Generické třídy – použití gen. typu Nesvázaný generický typ
Konstruovaný generický typ
Generické třídy – skutečný gen. typ
Skutečným typovým parametrem generického typu může být nejen skutečný datový typ, ale i konstruovaný typ, např. zásobník zásobníků int GenZasobnik<GenZasobnik<int>> nebo formální typový parametr, pokud je tento generický typ použit uvnitř deklarace jiného generického typu.
Konstruovaný gen. typ
otevřený konstruovaný typ (angl. open constructed type) – používá alespoň jeden formální typový parametr nějakého generického typu, např. GenZasobnik<T>.
zavřený konstruovaný typ (angl. closed constructed type) – má všechny typové parametry nahrazeny skutečnými datovými typy, např. GenZasobnik<int>, GenZasobnik<GenZasobnik<string>>.
Konstruovaný gen. Typ – příklad Příklad obsahuje fragment deklarace třídy Slovnik a její použití. Třída Slovnik má dva typové parametry, představující typ klíče a typ hodnoty. Tyto parametry jsou použity jako skutečné typové parametry dvou datových složek generického typu Vektor.
https://goo.gl/dDzCKw
V uvedeném příkladu je typ Slovnik<int, string> zavřený konstruovaný typ a typy Vektor<K> a Vektor<H> jsou otevřené konstruované typy.
Vnořené třídyTřída vnořená do generické třídy je automaticky také generická, protože má přístup k typovým parametrům vnější třídy. Opačně toto pravidlo neplatí. To znamená, že vnořená generická třída může být součástí generické i negenerické třídy.
class A<T> {
public class B { public T x; // OK
} } class Program {
static void Main(string[] args) { A<int>.B b = new A<int>.B(); b.x = 10; // OK
} }
Statické datové složky Statická datová složka generické třídy je společná pro všechny instance stejného zavřeného konstruovaného typu. Není společná pro odlišné zavřené konstruované typy. Toto pravidlo platí bez ohledu na to, zda daná statická datová složka je skutečného datového typu nebo typu typového parametru.
class A<T> { public static int Pocet; private T y; public A(T y) { this.y = y; Pocet++; }
} class Program {
static void Main(string[] args) { A<int> a1 = new A<int>(10); A<int> a2 = new A<int>(20); A<double> a3 = new A<double>(1.5); Console.WriteLine(A<int>.Pocet); Console.WriteLine(A<double>.Pocet); Console.ReadKey();
} }
Statický konstruktor Statický konstruktor generické třídy je volán pro každý zavřený konstruovaný typ zvlášť. Je volán v okamžiku, kdy je poprvé vytvořena instance daného zavřeného konstruovaného typu nebo kdy je poprvé použita některá statická složka daného zavřeného konstruovaného typu.
Je vhodným místem pro kontrolu omezení typového parametru, které nelze specifikovat v části omezení typových parametrů. Taková kontrola se však provede až za běhu programu.
Statický konstruktor – příklad Generická třída A požaduje, aby její typový parametr byl výčtového typu. Toto omezení kontroluje ve svém statickém konstruktoru a v případě chyby vyvolá výjimku.
https://goo.gl/5MABpK
Implementace genericity Genericita je v .NET podporována na úrovni mezijazyka IL a modulem CLR. Při překladu generické třídy do IL, bude v IL uveden jen jeden kód generické třídy s formálními typovými parametry.
Až překladač JIT překládá generickou třídu do nativního kódu nahrazením formálních typových parametrů skutečnými parametry. Výsledný kód závisí na typu skutečného typového parametru:
Pro každý hodnotový typ vytvoří samostatnou třídu, avšak žádné zabalení a vybalení instance hodnotového typu se neprovádí.
Pro všechny referenční typy vytvoří jednu třídu, v níž bude typový parametr nahrazen typem object, avšak žádné přetypování se neprovádí.
Implementace genericity Při použití generické třídy místo normální třídy s typem object se uvádí zvýšení výkonu:
u hodnotových typů až o 200 % (neprovádí se zabalení a vybalení),
u referenčních typů až o 100 % (neprovádí se přetypování).
Implicitní hodnoty Proměnným, jejichž typ je dán typovým parametrem, nelze přiřadit hodnotu null. - skutečným typovým parametrem může být také hodnotový typ a hodnota null je povolena pouze pro referenční typy.
K tomuto účelu lze použít výraz default (angl default value expression).
výraz_default:
default ( typ )
Typ – jméno typu nebo formálního typového parametru generického typu.
Implicitní hodnoty Pokud typ reprezentuje referenční typ, výsledkem výrazu je hodnota null. Pokud představuje hodnotový typ, výsledkem je implicitní hodnota pro daný hodnotový typ, což je:
0 pro číselné a výčtové typy,
false pro typ bool,
null pro nulovatelné typy,
výsledek volání implicitního konstruktoru pro uživatelem definované struktury – nastavení implicitních hodnot pro všechny datové složky.
Generická rozhraní deklarace generického rozhraní:
modifikátorynep partialnep interface jméno seznam_typových_parametrů předeknep omezení_typových_parametrůnep { složky_rozhraní } ;nep
Typové parametry a omezení typových parametrů mají stejný význam jako u deklarace generické třídy nebo struktury.
Generická rozhraní – příklad Příklad demonstruje deklaraci generického rozhraní zásobníku a jeho implicitní implementaci v generické třídě GenZasobnik.
https://goo.gl/hFjMZe
Generická rozhraní Generické rozhraní samozřejmě může implementovat i negenerický typ. Např. class IntZasobnik : IZasobnik<int> { public void Vloz(int obj) { ... } public int Odeber() { ... } }
Generická rozhraní Explicitně implementované složky generického rozhraní se kvalifikují konstruovaným generickým rozhraním, např. class ExplicitGenZasobnik<T> : IZasobnik<T> { // ... void IZasobnik<T>.Vloz(T obj) { ... } T IZasobnik<T>.Odeber() { ... } }
Generické delegáty deklarace generického delegátu:
modifikátornep delegate typ jméno seznam_typových_parametrů ( seznam_parametrůnep ) omezení_typových_parametrůnep ;
Typové parametry a omezení typových parametrů mají stejný význam jako u deklarace generické třídy nebo struktury.
Generické delegáty – příklad V příkladu je použita generická třída GenZasobnik z předchozího příkladu. Obsahuje navíc metodu Najdi, která hledá první výskyt prvku v zásobníku, pro který zadaný predikát vrací hodnotu true. Predikát je realizován generickým delegátem.
Zásobník je v hlavní metodě použit pro uložení řetězců znaků načtených z klávesnice. Pomocí metody Najdi se hledá první výskyt řetězce, který začíná zadaným textem. Skutečným parametrem metody Najdi je výraz lambda.
Příkaz volání metody Najdi by bylo možné také zapsat pomocí dvou příkazů např. s použitím anonymní metody:
Predikat<string> predikat = delegate(string hodnota) { return hodnota.StartsWith(pocatek); }; string text = zasobnik.Najdi(predikat);
https://goo.gl/b3OMXG
Generické metody Generická metoda je metoda, jejíž deklarace zahrnuje seznam typových parametrů a případné jejich omezení. Její syntaxe deklarace je následující:
deklarace generické metody:
modifikátorynep typ jméno seznam_typových_parametrů ( seznam_parametrůnep ) omezení_typových_parametrůnep tělo
Typové parametry a omezení typových parametrů mají stejný význam jako u deklarace generické třídy nebo struktury.
Generické metody Generická metoda obsahuje za jménem metody v lomených závorkách formální typové parametry oddělené čárkou. Pro jednotlivé typové parametry může obsahovat omezení, které se uvádějí až za seznamem formálních parametrů metody.
Formální typový parametr generické metody lze použít jako návratový typ, typ formálního parametru metody nebo v těle metody.
Generická metoda může být deklarována v generické i negenerické třídě, struktuře nebo rozhraní. Je-li deklarována v rámci generického typu, mohou být v ní použity i typové parametry tohoto generického typu.
Generické metody – příklad Třída Obecne obsahuje statickou generickou metodu Vymena, která vymění hodnoty dvou proměnných.
https://goo.gl/tYajjI
Generické metody Při přetěžování metod se do signatury metody nezahrnuje ani omezení typových parametrů ani jména typových parametrů. Rozhodují jen pravidla platná pro přetěžování negenerických metod, tj. počet, typ, pořadí a způsob předávání parametrů. Rozlišuje se ale stejně pojmenovaná generická a negenerická metoda. Např. nemohou být deklarovány dvě uvedené metody f.
class A { void f<T>(T x) { } void f<U>(U x) { } // Chyba – existuje již f<T> }
Generické metody Předefinovaná virtuální generická metoda nesmí obsahovat žádná omezení typových parametrů. Zdědí tato omezení z původní virtuální metody.
Do instance delegátu lze přiřadit i odkaz na generickou metodu. Generická metoda se v takovém případě uvádí jejím jménem, za nímž se v lomených závorkách specifikují skutečné typové parametry. Typové parametry se mohou vynechat, potom se odvodí z parametrů delegátu, do kterého se generická metoda přiřazuje.
https://goo.gl/0ia1wG
Direktiva using Direktivu using pro deklaraci nového jména pro daný typ lze použít jen pro zavřený konstruovaný typ. Např.
using SlovnikIS = Slovnik<int, string>; // OK using SlovnikX = Slovnik; // Chyba class Program { static void Main(string[] args) { SlovnikIS slovnik = new SlovnikIS(); slovnik.Pridej(1, "text"); // ... } }
Omezení typových parametrů V deklaraci generického typu nebo generické metody lze specifikovat omezení pro typové parametry.
Omezení je nutné specifikovat pro typový parametr, pokud je potřebné přistupovat k jeho složkám, např. volat jeho metodu. Pokud typový parametr nemá specifikována žádná omezení, lze s ním pracovat, jako by byl typu object.
Omezení typových parametrů – syntaxe
specifikace_omezení_typového_parametru
specifikace_omezení_typového_parametru omezení_typových_parametrů
specifikace_omezení_typového_parametru:
where typový_parametr : omezení_typového_parametru
omezení_typového_parametru:
primární_omezení
seznam_sekundárních_omezení
konstruktorové_omezení
seznam_omezení
Seznam omezení – seznam maximálně tří druhů omezení oddělených čárkou v pořadí primární, sekundární a konstruktorové.
Primární omezení Primární omezení pro typový parametr má následující syntaxi:
typ_třídy
class
struct
Typ_třídy – jméno typu třídy.
Omezení – příklad Je dána třída entity Entita, mající vlastnost Id. Od ní je odvozena třída uživatele Uzivatel. Dále je deklarována generická třída EntitaSeznam zapouzdřující pole entit a obsahující metodu NajdiId, která hledá index entity v poli podle zadaného Id. Aby se mohlo v této metodě přistupovat k vlastnosti Id prvku pole, musí se pro typový parametr T specifikovat primární omezení určující, že T musí být typu Entita. Metoda Main demonstruje použití seznamu entit na seznamu uživatelů.
https://goo.gl/Y2l05I
Omezení – příklad – varianty Pokud by třída EntitaSeznam neobsahovala specifikaci omezení pro typový parametr, příkaz #1 by mohl být nahrazen příkazem
if ((item as Entita).Id == id) return index; // OK Avšak následující příkaz by byl chybný:
if (((Entita)item).Id == id) return index; // Chyba
Sekundární omezení seznam_sekundárních_omezení:
typ_rozhraní
typový_parametr
seznam_sekundárních_omezení , typ_rozhraní
seznam_sekundárních_omezení , typový_parametr
Typ_rozhraní – jméno rozhraní.
Typový_parametr – jméno jiného formálního typového parametru.
Sekundární omezení Seznam sekundárních omezení může obsahovat seznam rozhraní oddělených čárkou, které daný typový parametr musí implementovat nebo musí být přímo typu uvedeného rozhraní. Dále může obsahovat jméno jiného typového parametru, kterému má být daný typový parametr roven nebo musí být jeho potomkem.
Např. následující generická třída A vyžaduje, aby typový parametr U byl stejného typu jako typový parametr T nebo byl jeho potomkem a zároveň, aby implementoval rozhraní ICloneable.
class A<T, U> where U : T, ICloneable { }
Sekundární omezení – příklad Předchozí přiklad je rozšířen o seřazení seznamu entit podle Id. Řazení provádí metoda Serad třídy EntitaSeznam, v níž se porovnávají dvě entity pomocí metody ComapareTo rozhraní IComparable. Toto rozhraní tudíž musí třída Entita implementovat a musí být specifikováno v omezení typového parametru třídy EntitaSeznam.
https://goo.gl/g36Crj
Sekundární omezení – příklad – varianty
Pokud by třída EntitaSeznam neobsahovala specifikaci omezení IComparable, příkaz #2 by mohl být nahrazen jedním z následujících příkazů:
if (((IComparable)pole[j]).CompareTo(pole[j + 1]) > 0) {
if ((pole[j] as IComparable).CompareTo(pole[j + 1]) > 0) {
Sekundární omezení – příklad – varianty
Při použití rozhraní IComparable se provádí přetypování mezi typem object a skutečným typem prvku pole. Pokud by prvkem pole byl hodnotový typ, provádělo by se zabalení a vybalení, což snižuje výkon aplikace. Proto od verze .NET 2.0 je k dispozici i generické rozhraní IComparable<T> deklarované v prostoru jmen System následovně:
public interface IComparable<T> { int CompareTo(T other) }
https://goo.gl/f5poNk
Konstruktorové omezení konstruktorové_omezení:
new ( )
Konstruktorové omezení specifikuje, že typový parametr reprezentuje referenční typ, který má veřejný bezparametrický konstruktor. Jen pomocí tohoto konstruktoru lze v generickém typu nebo metodě vytvořit novou instanci typu reprezentovaného formálním typovým parametrem.
Konstruktorové omezení Příklad:
class A<T> where T : new() { T x;
public A() { x = new T(); // #1 } }
Konstruktorové omezení class B<T> where T : struct { T x; public B() { x = new T(); // #2 } }
Doporučení pro pojmenováníFormální typové parametry by měly být pojmenovány podle následujících pravidel:
• Název typového parametru by měl začínat písmenem T.
• Lze-li typový parametr nahradit libovolným typem, protože pro něj není specifikováno žádné omezení a zároveň se používá pouze jeden typový parametr, lze použít samotné písmeno T.
Doporučení pro pojmenování•Je-li pro typový parametr specifikováno omezení, např. že musí implementovat nějaké rozhraní nebo musí být odvozen od dané třídy, nebo jestliže se používá více typových parametrů, měly by se použít popisné názvy.
Např. pokud typ typového parametru musí implementovat rozhraní IComparable, měl by se jmenovat TComparable, nebo pokud typ typového parametru musí být odvozen od třídy Entita, měl by se jmenovat TEntita:
class TridenySeznam<TComparable> where TComparable : IComparable
class EntitaSeznam<TEntita> where TEntita : Entita
class Dictionary<TKey,TValue> { }
Q & A