Tak jako celá vývojářská komunita, i my netrpělivě očekáváme příchod nové verze .NET 2 a vývojového prostředí MS Visual Studio 2005 známého pod označením „Whidbey“. Abychom věděli, co nás čeká a abychom s tím mohli seznámit i Vás, vyzkoušeli jsme betaverzi číslem 2. Dnes se seznámíme s první novinkou z řady vylepšení, která pro nás Microsoft připravil uvnitř programovacích jazyků C# a VB.NET.
Generics
Hned první „vychytávka“ stojí za pozornost. Značně totiž eliminuje práci s typem object, který je nechvalně známý svým použitím zejména v různých kolekcích typu Stack, ArrayList apod., kdy během kompilace není znám typ, který bude do kolekce přidáván. To vede jednak k tomu, že kompilátor neodhalí možné chyby v typech (a ty se projeví až za běhu) , jednak k tomu, že je nutno provádět boxing a unboxing proměnných z typu object a naopak (což snižuje výkon aplikace).
Jednoduchá ukázka nevýhodnosti typu object je demonstrována v následujícím příkladu, který ve výsledku vede k chybě vyvolané za běhu aplikace:
// using System.Collections;     
  
//implementace zásobníku objektů Employee
Stack employees = new Stack();
// parametr je typu object       
// je použita implicitní konverze na object      
employees.Push(    
   new Employee() );
// návratový typ je object     
// musíme provést explicitní konverzi    
Employee employee =    
   (Employee) employees.Pop();
//implementace zásobníku objektů Integer     
Stack sizes = new Stack();
// Boxing     
sizes.Push( 42 );
// Unboxing    
int size1 = (int) sizes.Pop();
// aplikačně nekorektní kód, který však kompilátor zkopmiluje    
sizes.Push( 77 );    
sizes.Push( new Employee() );
// nyní nastane výjimka InvalidCastException    
int size2 = (int) sizes.Pop();    
   Řešením je v .NET 2 použití zmíněných generics – tedy jakýchsi šablon kódu, které umožňují specifikaci typu v okamžiku použití šablony.   
Šablona se deklaruje za použití speciálního operátoru T, který vyjadřuje typ, a pomocí “špičatých” závorek (deklarace však může být i složitější, viz. poslední příklad). Při použití šablony se T nahrazuje za konkrétní typ objektu.    
Výsledkem je typová kontrola kódu již v průběhu kompilace a eliminace boxingu a unboxingu proměnných na typ objekt. A jak tedy vypadá implementace naší kolekce za použití generics ?
// using System.Collections.Generic;
Stack< Employee > employees =    
   new Stack< Employee >();
// parametr je typu Employee     
// neprovádí se žádná konverze    
employees.Push(    
   new Employee() );
// návratový typ je Employee     
// není nutná konverze    
Employee employee =    
   employees.Pop();    
Stack< int > sizes =    
   new Stack< int >();
// boxing se neprovádí    
sizes.Push( 42 );
// unboxing se neprovádí    
int size1 = sizes.Pop();
sizes.Push( 77 );
// chyba, která je odhalena již kompilátorem     
sizes.Push( new Employee() );
// výběr z kolekce proběhne vždy korektně    
int size2 = sizes.Pop();    
Jak vytvořit vlastní třídu s použitím generics
V případě, že potřebujeme vytvořit vlastní třídu implementující generics, postupujeme obdobně jako při programování běžné třídy, připojíme však navíc deklarace pro substituci datového typu.
// deklaraci třídy doplníme o operátor <T> reprezentující typ konkrétní instance třídy     
public class MyStack< T >    
{    
   private T“ frames;    
   private int pointer = 0;
   public MyStack( int size )    
   {    
      frames = new T` size `;    
   }
   public void Push( T frame )    
   {    
      frames` pointer++ ` =    
         frame;    
   }    
public T Pop()    
   {    
      return    
         frames` –pointer `;    
   }    
}
// v deklaraci instance třídy uvedeme typ      
MyStack< int > s =    
   new MyStack< int >( 7 );
//kompilátor dovolí vkládat pouze typově korektní hodnoty    
for ( int f = 0; f < 7; ++f )    
   s.Push( f );
// vypíše ‘6 5 4 3 2 1 0 ‘     
for ( int f = 0; f < 7; ++f )    
   Console.Write(    
      s.Pop() + " " );    
Jak jsme si ukázali, mohou nám generics usnadnit práci s kolekcemi a zbavit nás nepříjemností s typem object. Je však třeba mít na paměti omezení generics, kterými jsou:
- Omezení tříd – typy použitých objektů musí být potomkem specifikovaného typu
 - Omezení rozhraní – typy použitých objektů musí implementovat sepcifikovaná rozhraní
 - Omezení konstruktoru – typy použitých objektů musí mít veřejný defaultní konstruktor
 
Výše uvedená omezení jsou patrná na jednoduchém příkladu:
//třída MyList definuje dva různé typy, proto jsou zapsány jako K,V     
// a navíc obsahují deklaraci dědičmosti resp. implementovaných rozhraní      
class MyList< K, V >    
   where K : IComparable,    
             IFormattable    
   where V : ValueBase, new()    
{    
   // …    
}
class ValueBase {}
class Widget : ValueBase {}
class Thing : ValueBase    
{    
   public Thing( int i ) {}    
}
// OK – integer implementuje IComparable a IFormattable a Widget je potomkem ValueBase a zároveň má i veřejný defaultní konstruktor Widget()
MyList<int, Widget> list1 =    
   new MyList<int, Widget>();
// Chyba – string není potomkem ValueBase     
MyList<int, string> list2 =    
   new MyList<int, string>();
// Chyba – Thing nemá veřejný defaultní konstruktor, resp. kompilátor ho     
//nevygeneruje protože je uveden jiný konstruktor    
MyList<int, Thing> list3 =    
   new MyList<int, Thing>();
// Chyba – Point neimplementuje IComparable a IFormattable     
MyList<Point, Widget> list4 = new MyList<Point,Widget>();    
Jak dnešní úvodní díl našeho seriálu naznačuje, rozhodně je na co se těšit. A to je teprve malý závdavek. Příště se podíváme rovnou na několik novinek najednou – čekají nás např. partial classes nebo anonymní delegáti.
Autor příspěvku: Petr Hradec