Horké novinky v jazyce C# 2.0 (2. díl)

K zajímavým novým konstrukcím jazyka C# patří mimo jiné částečně deklarované typy a bezejmenné metody. Dle plán...


K zajímavým novým konstrukcím jazyka C# patří mimo jiné částečně deklarované
typy a bezejmenné metody.
Dle plánů Microsoftu se má jazyk C# stát hlavním vývojovým nástrojem v nové
verzi operačního systému Windows. Zatímco vývojové prostředí Visual Studio 2005
(Whidbey) se teprve připravuje, nová verze samotného programovacího jazyka C#
je již na světě a podle ohlasů veřejnosti nikdo neočekává, že by plán přechodu
z C++ a Javy na "Whidbey C#" neměl být úspěšný.
V tomto miniseriálu navážeme na nedávno uveřejněné povídání o generickém
programování (CW 42/2004) a představíme si řadu dalších novinek, které se
objevily v jazyce C# 2.0 a budou také součástí Visual C#/Visual Studio .Net
2005.

Iterátory
Uveďme si nejprve jednoduchou generickou třídu pro datovou strukturu zásobník.
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];
}
}
Pokud bychom chtěli do takové třídy (kolekce) přidat také podporu procházení
všech prvků klasickým příkazem foreach, ve staré verzi C# by to vyžadovalo
implementovat v naší třídě rozhraní IEnumerable čili metodu vracející
IEnumerator. Naše datová struktura je velmi jednoduchá, ale u složitějších
datových struktur může být (a často skutečně je) implementace enumerátorů velmi
pracná. Nyní však lze podporu procházení všech prvků kolekce příkazem foreach
zajistit i novým, mnohem jednodušším způsobem pomocí iterátorů.
Iterátor je programový blok, ve kterém se vyskytuje příkaz yield. (Blok je
libovolná část kódu mezi složenými závorkami.) Může to být tělo metody,
operátoru nebo accessoru (neboli přistupovače). Uveďme tedy kód iterátoru právě
pro náš generický zásobník:
class Stack<T> : IEnumerable<T> {
public IEnumerator<T> GetEnumerator() {
for(int i=0; i<pos; i++) {
yield return data[i];
}
}
}
Do deklarace zásobníku jsme přidali informaci, že budeme implementovat
generické rozhraní IEnumerable<T>, což je generická varianta klasického
rozhraní IEnumerable. Toto je tedy stejné jako v případě enumerátoru, rovněž
deklarace metody GetEnumerator se od klasické implementace (generického)
enumerátoru neliší. Tělo této metody zde však obsahuje příkaz yield, a právě
proto se stává iterátorem. Procházíme zde naše vnitřní pole, kterým
implementujeme zásobník, a příkazem yield postupně jakoby "vracíme" jednotlivé
prvky uložené na zásobníku. A tím jsme hotovi, náš zásobník nyní podporuje i
příkaz foreach.

Jak iterátory fungují
Překladač přeloží kód iterátoru do podoby přepínání kontextů. Ukažme si tedy
způsob provádění kódu, který používá náš iterátor.
foreach(string v in s) { Console.WriteLine(v);
}
Vlastní vykonávání kódu probíhá takto:
1.Na začátku je zavolána metoda s.Get Enumerator().
2.V místě příkazu yield je provádění kódu pozastaveno, je uchován kontext a
hodnota data[i] je dosazena do proměnné v.
3.Je provedeno tělo příkazu foreach (je vypsána hodnota v).
4.Kontext je přepnut zpět do metody GetEnumerator a vykonávání pokračuje za
příkazem yield opět až po další příkaz yield (můžeme tedy také umístit několik
yield příkazů na různá místa v metodě).
5.Body 2 až 4 se opakují, dokud neskončí provádění metody GetEnumerator. Tím je
ukončeno i provádění foreach.
Zbývá dodat, že příkazem yield break; ukončíme vykonávání kódu iterátoru (nelze
použít return, neboť metoda s iterátorem je deklarována jako vracející
IEnumerable).

Pojmenované iterátory
Již bylo řečeno, že iterátorem může být i tělo (pojmenované) metody. Uveďme
tedy příklad: Vytvoříme iterátor vracející pro dané N mocniny dvojky od 21 až
po 2N.
IEnumerable Power(int N) {
int counter = 0;
int result = 1;
while(counter++ < N) {
result *= 2;
yield return result;
}
}
Tento pojmenovaný iterátor použijeme takto (vypíšeme prvních 10 mocnin dvojky,
kód umístíme do jiné metody téže třídy):
foreach(int i in Power(10)) {
Console.WriteLine(i);
}
Tímto způsobem tedy můžeme iterátory použít i privátně (bez samostatné třídy)
nebo naopak umístit více různých typů iterátorů do jedné třídy (základní
iterátor se bude jmenovat klasicky GetEnumerator, další si pojmenujeme dle
vlastního uvážení).

Částečně deklarované typy
Doposud bylo nutné mít deklaraci celého typu (tj. třídy, struktury či rozhraní)
na jednom místě, v jednom souboru. U krátkých programů je to jistě příjemnější,
než způsob používaný v C++, avšak u delšího kódu nebo při používání generátorů
(jako je například editor formulářů ve Visual Studiu) tak vznikají velmi dlouhé
a nepřehledné soubory se zdrojovým kódem.
Tento problém je nyní možné odstranit použitím částečně deklarovaných typů
(neboli neúplných deklarací). Můžeme tak například oddělit část kódu tvořenou
generátorem od kódu psaného ručně. Používání tohoto prvku je navíc velmi
jednoduché, stačí použít klíčové slovo partial.
partial class MyClass {...}
Takto můžeme sloučit z libovolného počtu souborů (nebo z opakovaných deklarací
v témže souboru) deklarace tříd, rozhraní či struktur, a to včetně jejich
vnitřních součástí, atributů i XML komentářů. Můžeme dokonce i vynechávat
bázovou třídu a uvádět neúplné seznamy implementovaných rozhraní:
partial class MyClass : BaseClass, Interface1 {
partial class Inner<T> {
T a;
}
}
partial class MyClass : Interface2 {
partial class Inner<T> {
T b;
}
}
Používání částečných deklarací má jen několik málo omezení:
1.Je-li něco deklarováno jako partial, musí to tak být deklarováno pokaždé.
2.Všechny části deklarace musejí být umístěny v jednom modulu (potažmo
seskupení).
3.Jména částečných tříd i generických typů musejí být ve všech částečných
deklaracích stejná.
4.Přístupová práva a další modifikátory musejí souhlasit u všech částí
deklarace.
Zbývá dodat, že Visual Studio 2005 tento nový prvek jazyka C# používá opravdu
masivně, takže při vytváření Windows. .Forms okenních aplikací je námi psaný
kód striktně oddělen od automaticky generovaného kódu zajišťujícího chod
vizuálních částí programu. To je velmi příjemná změna.

Nulovatelné typy
Jak známo, hodnotové typy nepoužívají reference, nelze jim proto přiřadit null.
Většinou to nemá žádný negativní vliv, někdy však může být taková nepřiřazená
reference využita k jiným účelům. Například při práci s databázemi můžeme chtít
nějak označovat "neuvedené" hodnoty. Chceme-li mít takovou neuvedenou hodnotu
například u typu int, pak nezbývá, než jí rezervovat některou z nepoužitých
hodnot. To může být například 0 nebo -1, ale používání podobných konstrukcí
komplikuje kód a často vede k celé řadě (lidských) chyb.
Nyní máme k dispozici takzvané nulovatelné typy (nullable types). Jsou to
hodnotové typy, kterým také můžeme přiřadit hodnotu null. Nulovatelnou
proměnnou deklarujeme uvedením příslušného (libovolného) hodnotového typu a
přidáme otazník. Také základní použití nulovatelných proměnných je poměrně
snadné.
int? x;
Console.WriteLine(x==null ? "null" :
x.Value.ToString());
if(x.HasValue) Console.WriteLine("x má hodnotu");
Nulovatelné typy mají tyto vlastnosti:
Syntaxe T? pouze zastupuje delší zápis System.Nullable<T>. Tyto dvě konstrukce
jsou tedy ekvivalentní.
Vlastnost HasValue slouží ke zjištění, zda má daná proměnná hodnotu (HasValue
vrací true), nebo je null (HasValue vrací false).
Vlastnost Value zpřístupňuje hodnotu (typu T).
Převod na bázový datový typ (T) je možný explicitním přetypováním, převod z
bázového typu je dokonce implicitní.
int a;
int? x;
x = a;
a = (int)x; //vyvolá výjimku, když x bude null
Nulovatelné typy definují všechny operátory stejně jako bázový typ. Výsledkem
operací, do kterých vstupuje null hodnota, je vždy null hodnota. Uvedeme jen
několik příkladů.
double? a,b;
long? c;
a = b * 2.0;
c++;
if(a > c) {}
Pro pohodlnou práci můžeme použít také operátor ?? (dva otazníky), který
umožňuje definovat výchozí hodnotu při přetypování na bázový typ.
int? x;
int y = x ?? -1;
V této ukázce přiřazujeme do proměnné y hodnotu proměnné x. Pokud by x bylo
null, přiřadíme do y hodnotu -1. Operátor ?? můžeme použít i pro přiřazování
mezi nulovatelnými proměnnými.
int? z = x ?? -1;

Přestože je z nulovatelná proměnná, tímto příkazem do ní vložíme vždy nenulovou
(not null) hodnotu. (Operátor ?? totiž převede hodnotu na bázový typ int, při
následném přiřazení do int? je pak použit implicitní konverzní operátor.)

Pozor na typ bool?
Typ bool? má mezi nulovatelnými typy specifické postavení. Od ostatních se liší
tím, že jeho operátory logického součtu | (svislá čára = or) a logického
součinu & (anglický znak ampersand = and) dávají v některých případech nenulové
výsledky, i když jeden z operandů je null. Toto chování přesně odpovídá
klasické (matematické) trojstavové logice i chování trojstavové logiky v jazyce
SQL, proto se u něj nebudeme dále podrobněji zastavovat.

Hvězdička je trnem v oku
Nad nulovatelnými typy se strhla bouřlivá diskuze na internetu. Na jedné straně
totiž zjednodušují kód, zejména práci s relačními databázemi, na druhé straně
však přinášejí několik sporných bodů:
Deklarace ve tvaru int? mnoha lidem vadí. Především kvůli tomu, že jde jen o
syntaktický cukr, se programátoři rozdělili na dva proti sobě stojící tábory.
Zástupci prvního tábora milují C a odvozené jazyky pro jejich stručný zápis
mnoha konstrukcí (viz operátor ?:), druhý tábor naopak považuje toto zbytečné
zkracování deklarací za prvek znepřehledňující kód.
Nulovatelné typy jsou jen předkrokem k zavedení plné podpory proměnných ve
formě regulárních výrazů. Výraz int? totiž můžeme chápat jako "žádný nebo jeden
int". Potom int! by odpovídalo int a znamenalo by "právě jeden int". Dále int+
by znamenalo "jeden nebo více intů" a int* by znamenalo "libovolný počet intů".
Tyto konstrukce mají umožnit pohodlnější práci s daty. Na druhé straně také
přinesou zesložitění jazyka C# a je zde riziko, že méně zkušení programátoři je
ani nepochopí (nelze očekávat, že všichni znají regulární výrazy). Největší
odpor je proti konstrukci int*, která je pro svou zaměnitelnost se zcela
odlišným prvkem jazyka C++ mnoha lidem vyloženě trnem v oku.
Zatímco zde byl vyřešen problém nulovatelných typů, ticho zůstalo v otázce
"nenulovatelných" typů, jak je známe v C++. Jazyk C# totiž dodnes neobsahuje
konstrukci, jak zapsat kód syntakticky i sémanticky ekvivalentní referencím v
jazyce C++, tedy referenčním proměnným, které nemohou obsahovat null. Jak
ukazují příkladové studie, přidáním tohoto prvku by se podstatným způsobem
zvýšila bezpečnost i jednoduchost kódu, neboť na většině míst opravdu null
hodnoty (místo skutečných objektů) vůbec nechceme.

Bezejmenné metody
Bezejmenné (anonymní) metody představují především přiblížení jazyka C# k
funkcionálním jazykům. Nyní můžeme vzít "kus kódu" a předat ho jako parametr.
Tím vznikne metoda, která nemá jméno, ale můžeme ji používat díky referenci,
kterou na ni máme. Tato konstrukce již existuje v jazyce Java, takže zřejmě
nikoho nepřekvapí.
V C# se k odkazování na kód používají metody (ty deklarují onen kód) a delegáty
(ty na tento kód odkazují). Nyní tedy můžeme používat anonymní metody všude
tam, kde je očekáván delegát. Podobně jako iterátory zjednodušují jinak
komplikovanou práci s enumerátory, bezejmenné metody mohou často zjednodušit
práci s delegáty.
button.Click += delegate
{MessageBox.Show("Klik!");};
Tímto jednoduše přidáme kód uvedený ve složených závorkách (zobrazení zprávy
"Klik!") na událost stisku tlačítka. Ukázka rovněž předvádí základní místo
využití bezejmenných metod: Hodí se především tam, kde potřebujeme jen krátký
kód (například jediný příkaz, jako zde) a psaní klasických metod a delegátů by
bylo zbytečnou prací navíc.
Kód bezejmenné metody je vždy samostatným blokem odděleným od bloku, kde je
bezejmenná metoda deklarována. Vnitřní proměnné bezejmenné metody tedy nejsou
přístupné v nadřazeném bloku, můžeme zde však používat lokální proměnné
nadřazeného bloku (netýká se ref a out proměnných). Tyto proměnné nazýváme
vnější (outer) a jejich platnost je narozdíl od běžných lokálních proměnných
rozšířena na dobu platnosti bezejmenné metody (což je pochopitelné). Vnější
proměnné jsou tedy plně sdílené mezi anonymní metodou a vnějším blokem, což nám
umožňuje vytvářet poměrně krkolomné programové konstrukce.
int a = 0;
button.Click += delegate
{MessageBox.Show((++a).ToString());};
...
Tento kód zobrazí při každém stisku tlačítka o jedna větší číslo než minule. Na
místě tří teček můžeme proměnnou a používat také. Všechna místa použití této
proměnné přitom pracují s jedinou fyzickou instancí této hodnotové proměnné.

Operátor ::
Operátor :: je novinkou, která rozšiřuje jazyk C# o prvek, který dřívějšími
prostředky nebylo možné nijak nahradit. Ačkoliv jeho použití bude zřejmě velmi
okrajové, jeho přítomnost je jistě potěšující.
Pro minimalizaci nebezpečí záměny jmen je doporučováno používat plně
kvalifikovaná jména (například System.Console.WriteLine místo
Console.WriteLine). Pokud se však v kódu vyskytuje například následující
deklarace, použití plně kvalifikovaného jména nestačí.
int System,Console;
Tato deklarace efektivně znemožní vypisování čehokoliv na obrazovku. Nyní však
můžeme použít operátor :: (dvě dvojtečky) pro přístup ke globálním jménům.
System.Console.WriteLine("Nefunguje!"); //toto fungovat nebude
global::System.Console.WriteLine("Funguje!"); //toto funguje
Nutno upozornit, že funkce operátoru :: je zde odlišná od jazyka C++. Je to
binární operátor, na levé straně uvádíme obvykle slovo global reprezentující
globální prostor jmen, můžeme také uvést zástupné jméno prostoru jmen. V druhém
případě má operátor :: podobný význam jako operátor . (tečka), avšak omezuje
hodnotu na levé straně pouze na zástupná jména (namespace aliases).

Kompatibilita zůstává
Žádná z novinek C# naštěstí neovlivňuje fungování dosavadního kódu, takže kdo
je používat nechce, tak většinou nemusí. Slovo "většinou" zdůrazňujeme, neboť
vyhýbání se novým konstrukcím nejspíše povede mimo jiné k psaní nestandardního
kódu. Knihovna tříd .Net Frameworku (BCL) i Visual Studio 2005 totiž tyto nové
konstrukce aktivně používají a preferují

Vybrané odkazy na web
1. MSDN centrum nápovědy a dokumentace:
msdn.microsoft.com
2. MSDN Lab. laboratoř pro beta verze produktů apod.:
lab.msdn.microsoft.com
3. MSDN Magazine časopis serveru MSDN:
msdn.microsoft.com/msdnmag/
4. Visual Studio 2005 Beta Documentation:
msdn2.microsoft.com









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