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