HorkÚ novinky v jazyce C# 2.0 (1. dÝl)

Novß verze jazyka C# obsahuje celou řadu zajÝmavřch novinek. Tou nejzajÝmavýjÜÝ je podpora generickÚho programovßnÝ...


Novß verze jazyka C# obsahuje celou řadu zajÝmavřch novinek. Tou nejzajÝmavýjÜÝ
je podpora generickÚho programovßnÝ.
Novß verze jazyka C# hřbe svýtem. A to i přesto, ×e ji oficißlnÝ uvedenÝ Ŕekß
a× na zaŔßtku přÝÜtÝho roku. Na webu Microsoftu je toti× ji× k dispozici zcela
zdarma překladaŔ i s testovacÝ verzÝ vřvojovÚho prostředÝ Visual C# 2005.
NejzajÝmavýjÜÝm novřm prvkem jazyka je podpora generickÚho programovßnÝ, kterÚ
mß přiblÝ×it jeho mo×nosti tomu, co nabÝzÝ jazyk C++ v podobý Üablon.
Standard jazyka C# verze 2.0 je ji× delÜÝ dobu oficißlný schvßlen a nemýl by se
dßle mýnit. ZatÝmco Microsoft pokraŔuje v přÝpravý novÚ verze vřvojovÚho
prostředÝ Visual Studio 2005, překladaŔ C# 2.0 je i s prvnÝ verzÝ novÚho
přÝjemnÚho vřvojovÚho prostředÝ volný ke sta×enÝ na webovÚ adrese
lab.msdn.microsoft.com. A co tedy novß verze přinßÜÝ, ŔÝm se generickÚ
programovßnÝ liÜÝ od Üablon znßmřch z C++?

Generickř = obecnř
Anglickř termÝn "generics" obvykle v týchto souvislostech překlßdßme jako
"generickÚ typy", neboŁ pou×ÝvßnÝ "obecnÚ typy" by mohlo svßdýt k zßmýný s
jinřm vřznamem tohoto pojmu. Neformßlný se takÚ Ŕasto hovořÝ o "generikßch"
nebo "Üablonßch". Posledný jmenovanř pojem mß p¨vod v jazyku C++, kde pro
generickÚ programovßnÝ slou×Ý prßvý takzvanÚ Üablony.
GenerickÚ programovßnÝ je zřejmý nejd¨le×itýjÜÝm a takÚ nejslo×itýjÜÝm novřm
prvkem jazyka C#. Generickřmi se toti× mohou stßt třÝdy, struktury, rozhranÝ i
jednotlivÚ metody, co× se vřrazný projevilo i na kolekci zßkladnÝch třÝd .Net
Frameworku (BCL), na kterřch je výtÜina program¨ v C# i Visual Basic .Net
zalo×ena.
HlavnÝm cÝlem generickřch typ¨ (tj. třÝd, struktur a rozhranÝ) a metod je
umo×nit vyÜÜÝ ·rove˛ znovupou×itÝ kˇdu. SvÚ uplatnýnÝ najdou vÜude tam, kde se
opakuje stejnř kˇd nýkolikrßt pro r¨znÚ datovÚ typy. PřÝkladem m¨×e břt
klasickß Üablona v C++, kterß definuje generickou funkci swap pro zßmýnu hodnot
dvou promýnnřch libovolnÚho (ale tÚho×) typu. V C# vypadß kˇd swap takto (je to
metoda, umÝstÝme ji do libovolnÚ třÝdy):
void swap<T>(T a, T b) {
T c = a;
a = b;
b = c;
}
T zde oznaŔuje parametr datovř typ, kterř dosadÝme a× při pou×itÝ tÚto
generickÚ metody. NapřÝklad pro zßmýnu hodnot dvou promýnnřch typu int bychom
napsali:
int a,b;
swap<int>(a,b);//zßmýna a,b
swap(a,b); //zßmýna s pou×itÝm//typovÚ inference
Na poslednÝm řßdku ukßzky volßme generickou metodu bez uvedenÝ typu jejÝho
parametru. Tato konstrukce funguje dÝky typovÚ inferenci (neboli dedukci)
překladaŔ sßm urŔÝ typ podle dosazenřch parametr¨, podobný jako to umÝ v
přÝpadý pou×itÝ operßtoru ?: (otaznÝk dvojteŔka). V nejednoznaŔnřch situacÝch
je inference řeÜena přesný dle specifikace jazyka, nejlepÜÝ vÜak je se
jakřmkoliv nejednoznaŔnřm kombinacÝm typ¨ vyhnout.

Silný typovanÚ kolekce
GenerickÚ typy jsou tedy jakÚsi metatypy a teprve jejich instance jsou
klasickřmi typy. Typickou oblastÝ vyu×itÝ tohoto novÚho prvku jazyka je
deklarace silný typovanřch kolekcÝ. Za přÝklad nßm poslou×Ý datovß struktura
zßsobnÝku. Nejprve ukßzka kˇdu v C# 2.0:
class Stack<T> {
T[] data;
int pos;

public Stack() {
data = new T[10];
pos = 0;
}
public void push(T value) {
data[pos++] = value;
if(pos >= data.Length) pos=0;
}
public T pop() {
if(pos==0) pos = data.Length;
return data[-pos];
}
}
Tento kˇd definuje jednoduchou implementaci zßsobnÝku pomocÝ pole pevnÚ dÚlky.
Stack<T> je generickß třÝda, T je parametrem tÚto třÝdy, za kterř dosadÝme
konkrÚtnÝ znßmř datovř typ při vytvßřenÝ instancÝ naÜeho zßsobnÝku takto:
Stack<string> s = new Stack<string>();
Zde jsme vytvořili silný typovanou kolekci Stack<string> zßsobnÝk nad typem
string. Na tento zßsobnÝk tedy lze uklßdat jen objekty typu string, vÜechny
ostatnÝ objekty budou odmÝtnuty (přÝmo při překladu!). DÝky silný typovanřm
kolekcÝm ji× takÚ nemusÝme pou×Ývat boxovßnÝ (zabalovßnÝ hodnotovřch typ¨ do
objektu), neboŁ parametry generickÚho typu mohou břt jak referenŔnÝ, tak
hodnotovÚ typy. Pou×ÝvßnÝ generickřch třÝd pak vede jak k bezpeŔnýjÜÝmu, tak k
rychlejÜÝmu kˇdu (přetypovßnÝ a zvlßÜtý boxovßnÝ hodnotovřch typ¨ je velmi
"drahß" operace).

Kompilace a býh programu
Pro pochopenÝ detail¨ fungovßnÝ generickřch typ¨ a metod je vhodnÚ takÚ uvÚst,
jak jsou tyto programovÚ konstrukty přelo×eny.
U hodnotovřch typ¨ překladaŔ vytvořÝ novou konkrÚtnÝ třÝdu pro ka×dř hodnotovř
typ, kterř dosadÝme za parametr. Pou×Ývßme-li na vÝce mÝstech programu stejný
parametrizovanou generickou třÝdu, napřÝklad Stack<int>, překladaŔ pou×ije v×dy
stejnř kˇd. Kdy× ale potom pou×ijeme napřÝklad Stack<long>, je vytvořena novß
třÝda.
V programech se mohou bý×ný vyskytovat stovky třÝd, a kdyby programßtor chtýl
pro ka×dou z nich pou×Ývat urŔitou specifickou generickou třÝdu, znamenalo by
to vytvořit stovky instancÝ kˇdu tÚto generickÚ třÝdy, v×dy znovu pro ka×dř typ
dosazenř za parametr. Proto je překlad generickřch typ¨ v přÝpadý referenŔnÝch
parametr¨ ve skuteŔnosti provßdýn jinak.
PřekladaŔ vyu×Ývß toho, ×e samotnÚ reference na objekty jsou vÜechny stejný
velkÚ (v pamýti), tak×e ke ka×dÚ generickÚ třÝdý je vytvořena jen jedna
instance kˇdu pro vÜechny referenŔnÝ typy. Čili napřÝklad Stack<string> a
Stack<MyClass> budou sdÝlet jeden kˇd. Za býhu programu je samozřejmý mo×nÚ
pou×Ývat reflexi ke zjiÜtýnÝ konkrÚtnÝch typ¨.
Co zde bylo uvedeno pro generickÚ třÝdy, platÝ samozřejmý i pro ostatnÝ formy
generickÚho programovßnÝ (generickÚ metody, rozhranÝ a delegßty).

OmezenÝ typovřch parametr¨
ZatÝmco C++ Üablony m¨×eme pou×Ývat zp¨sobem hodný podobnřm textovřm makr¨m,
kde při překladu dojde k pouhÚmu nahrazenÝ textovÚho nßzvu parametru skuteŔnřm
typem, v C# to tak jednoduÜe nefunguje. D¨vodem je předevÜÝm vřÜe uvedenř fakt,
×e v přÝpadý referenŔnÝch parametr¨ je ke ka×dÚ generickÚ třÝdý vytvořena
(maximßlný) jedna instance kˇdu.
C# umoײuje na vÜech typovřch parametrech provßdýt jen obyŔejnÚ přiřazovßnÝ,
porovnßvßnÝ s null (u hodnotovřch typ¨ vracÝ test na null v×dy false) a volßnÝ
metod třÝdy System.Object. OstatnÝ výci lze realizovat jen s pou×itÝm klauzule
where T:..., kde za dvojteŔkou (mÝsto třÝ teŔek) napÝÜeme seznam vy×adovanřch
vlastnostÝ typu T. Klauzule where umoײuje tato konkrÚtnÝ pou×itÝ:
OmezenÝ na konkrÚtnÝ typ je vy×adovßno při volßnÝ konkrÚtnÝch metod Ŕi
operßtor¨. Toto omezenÝ se deklaruje uvedenÝm bßzovÚho typu nebo rozhranÝ, kde
jsou deklarovßny metody/operßtory, kterÚ chceme pou×Ýt.
Jako přÝklad uve´me generickou metodu, kterß volß na svÚm parametru metodu
test(). Aby to bylo mo×nÚ, musÝme deklarovat rozhranÝ s touto metodou a v
deklaraci generickÚ metody pak uvÚst po×adavek, aby typ parametru toto rozhranÝ
implementoval.
interface ITestable {
void test();
}
void test<T>(T obj) where T : ITestable { obj.test();
}
Zde je vidýt zßsadnÝ rozdÝl oproti C++, tam je toti× mo×no pou×Ýt Üablony i pro
realizaci obecnÚho polymorfismu (tj. na dvou r¨znřch typech lze volat stejný
pojmenovanou metodu, ani× by mýla stejnÚ parametry nebo pochßzela z typu ve
stejnÚm stromu dýdiŔnosti). V C# to mo×nÚ nenÝ.
VytvßřenÝ novřch objekt¨ vy×aduje uvedenÝ new() v seznamu vlastnostÝ.
T create<T>() where T : new() {
return new T();
}
Definovali jsme generickou metodu jmÚnem create. Metoda je bez vstupnÝch
parametr¨ a vracÝ typ T. Tato metoda (umÝstÝme ji do libovolnÚ třÝdy) vytvßřÝ
objekty typu danÚho parametrem T. Generickou metodu zavolßme tak, ×e uvedeme
mezi jejÝ jmÚno a vstupnÝ parametry takÚ konkrÚtnÝ typ, kterř mß břt dosazen za
T.
MyClass a = create<MyClass>();
Tento zp¨sob vytvßřenÝ objekt¨ lze pou×Ýt jen u konstruktor¨ bez parametr¨.
Potřebujeme-li konstruktor s parametry, pou×ijeme omezenÝ na konkrÚtnÝ typ, jak
je uvedeno vřÜe v přÝpadý volßnÝ metod.
OmezenÝ na pouze hodnotovÚ nebo pouze referenŔnÝ typy provedeme pou×itÝm
omezenÝ class nebo struct. Praktickř dopad týchto omezenÝ je minimßlnÝ, majÝ
slou×it zejmÚna pro ochranu před lidskřmi omyly.
class RefOnly<T> where T : class {...}
OmezenÝ class vynutÝ, aby na mÝstý typovÚho parametru byl referenŔnÝ typ a
umo×nÝ nßm pou×Ývat operßtor as. (Vřrazem var as Type rozumÝme zmýnu referenŔnÝ
promýnnÚ var na referenci na typ Type. NenÝ-li zmýna reference mo×nß, vřsledkem
je null. Na rozdÝl od klasickÚho přetypovßnÝ (casting) se zde provßdÝ jen zmýna
reference, bez pou×itÝ u×ivatelskřch přetypovßvacÝch metod.)
class ValueOnly<T> where T : struct {...}
OmezenÝ struct vynutÝ, aby na mÝstý typovÚho parametru byl hodnotovř typ a
znemo×nÝ pou×Ývat porovnßnÝ s null (kterÚ je vÜak mo×nÚ u hodnotovřch typ¨ v
generickÚ třÝdý bez omezenÝ!). Za struct se zde pova×ujÝ vÜechny hodnotovÚ
typy, tedy vŔetný int, char a podobný.
lVÝcenßsobnß omezenÝ lze uvßdýt oddýlenß Ŕßrkou. Jeliko× typovř parametr m¨×e
sßm břt souŔßstÝ deklarace omezenÝ pro jinř typovř parametr, lze vytvořit i
deklaraci, jako je tato:
class MyClass<A, B, C> where A : B where B : C {
}
Deklarovali jsme generickou třÝdu se třemi parametry a jako podmÝnku jsme
uvedli, ×e mezi typovřmi parametry je vy×adovßn vztah dýdiŔnosti. VÜimnýte si,
×e jednotlivÚ klauzule where nemajÝ mezi sebou ×ßdnř oddýlovaŔ (jen mezeru, ta
ale nenÝ syntaktickřm prvkem), editor Visual C# proto pro lepÜÝ přehlednost
zdrojovÚho kˇdu automaticky umisŁuje vÝcenßsobnß omezenÝ na samostatnÚ řßdky.
lOdhalenř (naked) parametr je ten, kterř je uveden jako omezenÝ jinÚho
parametru (viz přÝklad u předchozÝho bodu). Pou×itÝ odhalenřch parametr¨ je
dosti omezenÚ, lze jimi definovat jen vztah dýdiŔnosti mezi dvýma typovřmi
parametry.

DýdiŔnost
Typovř parametr nem¨×e břt pou×it jako bßzovß třÝda. NßsledujÝcÝ kˇd tedy
nefunguje.
class Outer<T> { class Inner : T {} //Chyba: TřÝda Inner //nem¨×e dýdit třÝdu T
}
GenerickÚ typy vÜak mohou břt předky ve stromu dýdiŔnosti. V tom přÝpadý musÝme
rozliÜit nýkolik přÝpad¨ u×itÝ generickÚ třÝdy.
//BaseType nßm poslou×Ý jako bßzovß třÝda
class BaseType {}
//Pozor! Tato generickß třÝda je dalÜÝ //·plný samostatnou třÝdou!
class BaseType<T> {}
//Type1 dýdÝ konkrÚtnÝ třÝdu BaseType
class Type1 : BaseType {}
//Chyba! Nelze zde zjistit typ parametru T
class Type2 : BaseType<T> {}
//Type3 je generickß třÝda dýdÝcÝ //z generickÚ třÝdy BaseType<T>
class Type3<T> : BaseType<T> {}
//Type4 je generickß třÝda dýdÝcÝ //z konkrÚtnÝ třÝdy BaseType<int>
class Type4<T> : BaseType<int> {}
V ukßzce je tedy vidýt, ×e potomek m¨×e břt bu´ konkrÚtnÝ třÝdou Type, nebo
generickou třÝdou Type<T>. NavÝc m¨×e mÝt r¨znÚ předky: MusÝme rozliÜovat mezi
konkrÚtnÝ třÝdou BaseType, generickou třÝdou BaseType<T>, a jejÝ instancÝ
(konkrÚtnÝ třÝdou) BaseType<int>.
Při dýdýnÝ generickřch třÝd obsahujÝcÝch omezenÝ (jak bylo popsßno v předchozÝ
sekci) musÝ generickř potomek definovat omezenÝ, kterß jsou nadmno×inou omezenÝ
předka nebo omezenÝ předka implikujÝ.

Co vÜechno m¨×e břt generickÚ
ZatÝm byla řeŔ předevÜÝm o třÝdßch, stejnřm zp¨sobem lze vÜak deklarovat i
generickß rozhranÝ a generickÚ struktury. PlatÝ samozřejmý obvyklß omezenÝ
týchto jazykovřch konstrukt¨ napřÝklad rozhranÝ nemajÝ konstruktory, struktur
se netřkß dýdiŔnost a podobný.
Jak bylo ukßzßno v ·vodu kapitoly, generickÚ mohou břt i metody. Opýt zde platÝ
přimýřený totÚ×, co u třÝd. Metody uvnitř generickřch třÝd mohou pou×Ývat
typovÚ parametry svřch třÝd (jinak by to ani nemýlo smysl).
class MyClass<T> {
//Chyba! V generickÚ metodý nelze uvÚst //stejnř typovř parametr. void
MyMethod<T>() {}
//Toto je v pořßdku pou×ijeme typ T //z mateřskÚ třÝdy. bool IsNull(T obj) {
return obj==null;
}
}
VřchozÝ hodnoty
Chceme-li přiřadit do promýnnÚ vřchozÝ hodnotu generickÚho typu, m¨×eme pou×Ýt
klÝŔovÚ slovo default. Tato novß konstrukce se netřkß jen generickřch typ¨, ale
prßvý tady najde nejvýtÜÝ uplatnýnÝ.
int a = default(int);
MyClass b = default(MyClass);
MyStruct c = default(MyStruct);
VřchozÝ hodnota referenŔnÝho typu je v×dy null. VřchozÝ hodnota ŔÝselnÚho typu
je binßrnÝ nula a u struktur se toto pravidlo aplikuje rekurzivný na vÜechny
vnitřnÝ polo×ky.

Analřza, shrnutÝ
GenerickÚ typy a metody se vřrazný podepsaly na novÚ verzi knihovny zßkladnÝch
třÝd .Net Frameworku (BCL), předevÜÝm v oblasti kolekcÝ. OcenÝ je předevÜÝm
programßtoři zvyklÝ na C++, neboŁ hodný připomÝnajÝ tamnÝ programovßnÝ se
Üablonami. OstatnÝ vřvojßři asi budou muset nejprve zÝskat s tÝmto novřm stylem
programovßnÝ urŔitou praxi, pak ale jistý budou překvapeni, jak vřraznÚho
zkrßcenÝ kˇdu (dÝky vyÜÜÝ ·rovni znovupou×itÝ) lze takto dosßhnout.
Proto×e s sebou generickÚ programovßnÝ přinßÜÝ urŔitß bezpeŔnostnÝ pravidla a
omezenÝ, vedou se o jeho přÝnosu takÚ dlouho trvajÝcÝ spory na internetu.
Programßtoři toti× mnohem radýji dýlajÝ výci "po svÚm", ne× aby dýlali výci
"jak je to sprßvnÚ". GenerickÚ typy a metody jsou naÜtýstÝ jen rozÜÝřenÝm
stßvajÝcÝch vlastnostÝ C# a i kdy× je jejich pou×ÝvßnÝ autory .Net Frameworku
velmi doporuŔovßno, nenÝ v¨bec povinnÚ. Kdo opravdu genericky neboli obecný
programovat nechce, tak prostý nemusÝ.

SilnýjÜÝ kontrola
GenerickÚ typy a metody vnßÜejÝ do program¨ silnýjÜÝ typovou kontrolu. To je
velmi prospýÜnÚ, na druhou stranu bude prßvý toto řadý programßtor¨ zvyklřch na
nepřÝliÜ ŔistÚ objektový orientovanÚ programovßnÝ vadit. Podobnř pocit
"otravnřch" omezenÝ mohou pociŁovat i C++ programßtoři zvyklÝ na neobvyklÚ
konstrukce u Üablon, kterÚ v C# povoleny nejsou. C++ napřÝklad umoײuje
pou×Ývat Üablony pro plnř polymorfismus (zßmýna metod stejnÚho jmÚna bez
deklarace spoleŔnÚho rozhranÝ a bez ohledu na typy jejich parametr¨, tedy de
facto na ·rovni textovřch maker), co× v jazyce C# mo×nÚ nenÝ.

VybranÚ odkazy na web

n
MSDN centrum nßpovýdy a dokumentace: msdn.microsoft.com
MSDN Lab. laboratoř pro beta verze produkt¨ apod.: lab.msdn.microsoft.com
MSDN Magazine Ŕasopis serveru MSDN: msdn.microsoft.com/msdnmag/
Visual Studio 2005 Beta Documentation: msdn2.microsoft.com









Komentáře
K tomuto článku není připojena žádná diskuze, nebo byla zakázána.