Hlavní navigace

Pojďte s námi PROGRAMOVAT !

1. 9. 2008

Sdílet

C# V zářijové lekci programátorského kurzu jazyka C# 3.0 vám představíme možnosti mechanizmu typové inference ...


C#

V zářijové lekci programátorského kurzu jazyka C# 3.0 vám představíme možnosti mechanizmu typové inference lokálních proměnných a seznámíme vás se strukturami coby uživatelsky deklarovanými hodnotovými datovými typy.



Inferenční mechanizmus a typová inference lokálních proměnných

Programovací jazyk C# dovede ve verzi 3.0 pracovat s inferenčním mechanizmem, který dokáže automaticky přisoudit lokálním proměnným nejvhodnější datové typy. Ve všeobecnosti termín „inference“ představuje „strojové odvozování“ neboli „strojovou dedukci“. Inferenční mechanizmus pak můžeme chápat jako softwarový stroj, jenž je schopen samočinně vykonat určitou množinu operací bez zásahu člověka. Jazyk C# 3.0 obsahuje inferenční mechanizmus pro implicitní stanovení datových typů lokálních entit. Těmito lokálními entitami mohou být proměnné nebo také pole. Abychom zachovali plynulou linii výkladu, v této lekci našeho programátorského kurzu se budeme věnovat pouze typové inferenci lokálních proměnných. S poli coby složenými datovými strukturami se seznámíme až později.
Na naší dosavadní pouti jsme viděli, jak se vytvářejí (definují) a inicializují proměnné primitivních hodnotových i odkazových datových typů. Přitom jsme zjistili, že každá správně definovaná proměnná má svůj datový typ a identifikátor. Ve všech uváděných ukázkových programech a praktických fragmentech zdrojového kódu jazyka C# 3.0 jsme se běžně setkávali s proměnnými, jež měly explicitně určené své názvy a datové typy. Rovněž jsme si vysvětlili, jaký je rozdíl mezi tzv. čistou definicí lokální proměnné a její definiční inicializací. Nyní své obzory rozšíříme a podíváme se, jak nám může inferenční mechanizmus pomoci.
První důležitá informace se vztahuje k množině lokálních entit, s nimiž smí být inferenční mechanizmus aktivován. Inferenční mechanizmus se totiž dostává ke slovu pouze ve speciálně zapsaných definičně-inicializačních příkazech lokálních proměnných. Když budeme chtít, aby datový typ lokální proměnné diagnostikoval kompilátor, použijeme následující generický vzor:

var X = IV;

kde:

• var je vyhrazené (klíčové) slovo jazyka C# 3.0, které říká, že datový typ lokální proměnné bude implicitně inferován.
• X je identifikátor lokální proměnné.
• IV je inicializační výraz, jehož hodnotou bude inicializována definovaná lokální proměnná.

Hlavním rozdílem, který jistě ihned v představeném příkazu zpozorujeme, je absence datového typu lokální proměnné. Determinace datového typu je substituována klíčovým slovem var, které podněcuje kompilátor k spuštění inferenčného mechanizmu. Otázka, zda bude datový typ lokální proměnné určen strojově nebo ne, však nemá žádný vliv na nutnost pojmenovat proměnnou. Samozřejmě, pojmenování lokální proměnné, jejíž datový typ bude určen za asistence inferenčného mechanizmu, se řídí stejnými nomenklaturními pravidly, které platí pro všechny standardní proměnné. Nejvýznamnější roli v generickém příkazovém vzoru hraje inicializační výraz, jenž se nachází na pravé straně definičně-inicializačního příkazu. Je to proto, že inferenční mechanizmus nevystupuje jako záhadný šaman, který vezme lokální proměnnou, tiše vyřkne zaříkávadlo a za potlesku obecenstva uhodne nejvhodnější datový typ proměnné. Kdepak: inferenční mechanizmus je stroj, který musí vždy vědět, jak krok za krokem vyřešit svěřený úkol. Dobrá, jak tedy při své práci inferenční mechanizmus postupuje? Nejprve je vyhodnocen inicializační výraz. Jestliže je inicializační výraz zapsán syntakticky a sémanticky správně, kompilátor dovede určit jeho hodnotu. Inferenční mechanizmus pak na základě typu hodnoty inicializačního výrazu přisoudí definované lokální proměnné nejpřiměřenější datový typ.

V následujícím výpisu zdrojového kódu jazyka C# 3.0 vidíme praktické nasazení inferenčního mechanizmu:

U proměnné x je situace jasná: poněvadž celočíselná konstanta disponuje implicitním typem int, inferenční mechanizmus přiřadí této proměnné stejný typ. Podobně se inferenční mechanizmus chová i v případě proměnné y, rozdíl je pouze v tom, že nejprve je nutné určit typ hodnoty zapsaného aritmetického výrazu x + 10. Jelikož typem proměnné x je inferovaný int a konstanta 10 je také celočíselná, bude hodnotou celého výrazu celé číslo 20 typu int. Samozřejmě, stejný typ bude přisouzen také proměnné y. Patrně nejzáhadněji vypadá poslední definiční inicializace. Zde má inferenční mechanizmus za úkol určit datový typ proměnné z. Opět začínáme analýzou inicializačního výrazu. Tímto výrazem je aritmetický výraz x * 2.05 – y, který bude podle pravidel priority a asociativity aritmetických operátorů vyhodnocován takto: (x * 2.05) – y. Jako první tedy kompilátor vyhodnotí multiplikativní podvýraz x * 2.05. Ačkoliv v proměnné x je uložena celočíselná hodnota typu int, reálná konstanta 2.05 implicitního typu double způsobí realizaci implicitní typové konverze.
Cílem implicitní typové konverze je sjednocení datových typů zúčastněných hodnot, poněvadž jejich typy nejsou kompatibilní (int ó double). V průběhu implicitní typové konverze je hodnota proměnné x přetypována na typ double. To znamená, že z celočíselné hodnoty 10 se stává reálná hodnota 10.0. Jelikož nyní jsou již typy hodnot stejné (double ó double), může kompilátor uskutečnit jejich součin. Hodnota součinu je 20.5 a je to reálná hodnota typu double. Ještě stále nejsme u konce, takže kompilátor pokračuje vyhodnocením podvýrazu 20.5 – y. Proměnná y je implicitně typu int, podobně jako hodnota 20 v ní uložená. Vzhledem k tomu, že opět narážíme na nesoulad datových typů (double ó int), neobejdeme se bez realizace další implicitní typové konverze. Hodnota celočíselného typu int s „nižší“ prioritou bude přetypována na reálnou hodnotu typu double s „vyšší“ prioritou. Finální hodnota aritmetického výrazu tedy bude reálná, bude mít podobu 0.5 a jejím typem bude double. Jakmile to inferenční mechanizmus zjistí, přisoudí typ double také definované proměnné z.

Prostudujme si další fragment zdrojového kódu:


Obě lokální proměnné postrádají explicitní specifikace svých datových typů, což znamená, že zodpovědnost za jejich určení převezme do své kompetence inferenční mechanizmus. Definiční inicializace proměnné a nepředstavuje problém: do této proměnné přiřazujeme celočíselnou konstantu, tudíž jejím implicitním typem bude int. Poutavější je beze sporu druhý příkaz, v němž manipulujeme s proměnnou b. Podívejme se na její inicializační výraz. Máme tu dvě řetězcové konstanty a jednu proměnnou typu int. Jak bude tento výraz vyhodnocen? Abychom situaci zjednodušili, rozdělíme celý inicializační výraz následujícím způsobem: ("Máme " + a) +
" míčků.". Když je aritmetický operátor + zapsán v tomto kontextu, kompilátor se bude snažit převést obsah proměnné a do podoby textového řetězce, a poté oba řetězce spojit do jednoho (ano, jde o operaci zřetězení textových řetězců). Výsledkem zřetězení bude text "Máme 5". To ale není všechno, neboť je zapotřebí provést ještě jedno zřetězení, jehož výsledkem bude textový řetězec "Máme 5 míčků.". Finální textový řetězec bude uložen do instance třídy System.String, a proto můžeme prohlásit, že inferenční mechanizmus udělá z proměnné b odkazovou proměnnou, jejímž implicitním typem bude primitivní odkazový typ string.

Restrikce v použití inferenčního mechanizmu

Mechanizmus typové inference je skvělým pomocníkem. Je však důležité vědět, že inference datových typů se může uskutečňovat pouze v definičně--inicializačních příkazech lokálních proměnných. Jinými slovy, inferenční mechanizmus nelze uplatnit v čistých definicích, tedy v ryze definičních příkazech, jež lokální proměnné sice vytvářejí, ovšem neinicializují. Hezká ukázka čisté definice proměnné je například takováto:

V definičních příkazech tohoto druhu budeme muset vždy zapisovat konkrétní datový typ proměnné, neboť inferenční mechanizmus nemá žádné informace, podle kterých by mohl typ automaticky determinovat.

Struktury

Struktury patří společně s vyjmenovanými typy k uživatelsky deklarovaným hodnotovým datovým typům jazyka C# 3.0. Na rozdíl od primitivních hodnotových typů jako je short, int, float nebo double, nejsou uživatelsky deklarované datové typy přímo vestavěny do jazykové specifikace C# 3.0. Když budeme chtít uživatelsky deklarovaný typ použít, musíme jej nejprve řádně deklarovat. Z ryze technické stránky jsou struktury agregovanými heterogenními uživatelsky deklarovanými hodnotovými datovými typy. Práce se strukturami je nepochybně složitější než manipulace s primitivními hodnotovými typy. Na druhou stranu, struktury s sebou přinášejí vskutku signifikantní přidanou hodnotu, neboť nám dovolují vytvářet abstraktní modely entit reálného světa. Když budeme chtít napsat program pro evidenci zaměstnanců, jako smysluplné se bude jevit uchovávat o každém zaměstnanci jisté údaje (jako je jméno, příjmení, mzda či počet odpracovaných let ve společnosti). V roli programátorů bude naším zájmem vytvoření nového datového typu Zaměstnanec, jehož instance budou působit coby konkrétní zaměstnanci. Kdybychom nahlédli do bázové knihovny tříd vývojově-exekuční platformy Microsoft .NET Framework 3.5, nenalezli bychom (ať už bychom byli sebevíc pečliví) žádný datový typ, který by nám dovoloval pracovat se zaměstnanci. To ovšem nevadí, protože pomocí vhodně deklarované struktury si takovýto typ připravíme sami.

Struktura se v jazyce C# 3.0 deklaruje podle následujícího generického modelu:

[m] struct X
{
// Tělo struktury.
}

kde:

• [m] je přístupový modifikátor struktury.
• struct je klíčové slovo jazyka C# 3.0, které uvádí deklaraci struktury.
• X je identifikátor struktury.
• Blok vymezený složenými závorkami ({}) tvoří tělo struktury.

Přístupový modifikátor ohraničuje oblast platnosti deklarované struktury. Na struktury uložené v jistém jmenném prostoru smí být aplikovány pouze dva přístupové modifikátory: public a internal. Modifikátor public říká, že deklarovaná struktura disponuje veřejným přístupem, což znamená, že k ní může přistupovat jakýkoliv klientský programový kód. O něco striktněji funguje modifikátor internal, jenž přiděluje struktuře interní přístupová práva. Struktura s modifikátorem internal je dosažitelná pouze pro zdrojový kód, který se nachází v sestavení aplikace .NET, v němž se tato struktura deklaruje. Dodejme, že jazyk C# 3.0 nevyžaduje explicitní specifikaci přístupového modifikátoru. Jestliže není užit žádný modifikátor, implicitně je struktura interní (jako kdyby byl zapsán modifikátor internal).
Klíčové slovo struct instruuje kompilátor, že aktuální blok kódu reprezentuje deklaraci struktury jako nového uživatelsky deklarovaného hodnotového datového typu. X je název struktury a platí pro něj stejná pojmenovávací pravidla jako pro proměnné, resp. jakékoliv jiné programové entity. Modifikátor přístupu společně s klíčovým slovem struct a identifikátorem struktury tvoří tzv. hlavičku struktury. Za hlavičkou struktury následuje tělo struktury, které je syntakticky ohraničeno blokem mezi složenými závorkami. Veškeré příkazy, které se budou v tomto bloku nacházet, patří do těla struktury. Vybaveni novými informacemi můžeme konstatovat, že deklarace struktury je složena z hlavičky a těla struktury. Představme si praktickou ukázku deklarace struktury. Naše struktura se jmenuje Vektor a její syntaktický obraz je následující:

Instancemi deklarované struktury budou vektory, které s povděkem využijeme třeba při algoritmizaci vybraných problémů z oblasti lineární algebry. Deklarovaná struktura je veřejná (přístupový modifikátor public), použít ji tudíž může kdokoliv. V těle struktury se nachází jeden příkaz, který zavádí definici tří členů struktury. Termínem „člen struktury“ se rozumí jakákoliv syntakticky korektně zapsaná programová entita, která se smí vyskytovat v těle struktury. Členy naší struktury jsou tři proměnné primitivního datového typu int.
Tyto proměnné slouží k uchování x-ové, y-ové a z-ové složky vektoru. Členy struktury, které mají za úkol uchovávat data, se nazývají datové členy struktury (nebo datové položky či datové atributy struktury). Každý člen struktury má jistá přístupová práva. Není-li určeno jinak, jsou všechny členy v deklaraci struktury implicitně soukromé. V našem případě jsou datové členy struktury veřejné, protože jsme při jejich definici použili přístupový modifikátor public. Ačkoliv podle teorie objektově orientovaného programování bychom měli ponechat datové členy soukromé, prozatím si můžeme dovolit pracovat s veřejnými datovými členy.
Deklarace struktury sice říká, že vektor bude mít tři složky, ovšem žádný vektor ve skutečnosti nevytváří. Na deklaraci struktury můžeme nahlížet jako na deklaraci nového abstraktního datového typu, nebo jako na šablonu, podle které budou zhotovovány skutečné vektory. Tyto skutečné vektory budou alokovány v paměti a z programového hlediska budou vystupovat jako objekty neboli instance struktury.
Instance struktury vzniká v procesu instanciace struktury. Nejprve si představíme instanciační příkaz struktury, pak si rozebereme všechny fáze instanciačního procesu. Generická podoba instanciačního příkazu struktury je takováto:

X obj = new X();

kde:

• X je identifikátor struktury.
• obj je název hodnotové (strukturové) proměnné.
• new je operátor zahajující instanciační proces.

Z akademických zkušeností vyplývá, že snadněji se dá instanciační příkaz struktury pochopit, když na něj budeme nahlížet jako na definiční inicializaci hodnotové proměnné s identifikátorem obj. Definiční inicializace je umožněna pomocí přiřazovacího příkazu, v němž je možné rozeznat tři části: levou stranu, operátor přiřazení (=) a pravou stranu. Začněme analýzou levé strany. Zde se vyskytuje výraz, který vytváří hodnotovou proměnnou obj. Typem této proměnné je struktura s názvem X. Z tohoto důvodu se tato hodnotová proměnná nazývá také strukturová proměnná, resp. proměnná, jejímž typem je struktura. Jelikož je strukturová proměnná proměnnou hodnotového datového typu (struktury), bude alokována na zásobníku programového vlákna. Přesněji, strukturová proměnná bude uchovávat instanci struktury, kterou v instanciačním příkazu založíme. Nyní naši pozornost věnujme pravé straně přiřazovacího příkazu. Výraz new X() spouští instanciační proces struktury. Novinkou je operátor new, který zabezpečí provedení následujících akcí:

Vytvoří instanci struktury X a uloží ji do adresového prostoru strukturové proměnné. Zjednodušeně tedy můžeme říct, že strukturová proměnná zapouzdřuje instanci struktury. Pravdou také je, že strukturová proměnná představuje symbolické pojmenování instance struktury. Schematické znázornění alokace instance struktury na zásobníku dokládá další obrázek.
Automaticky vyvolá konstruktor, který provede inicializaci alokované instance struktury. Konstruktor je speciální metoda, jejímž smyslem je uvést instanci struktury do výchozího, tedy okamžitě použitelného stavu.

Instanci naší struktury Vektor sestrojíme takhle:

Výsledkem příkazu bude vytvoření hodnotové proměnné v1, která bude obsahovat instanci struktury Vektor. Jelikož implicitní konstruktor uskuteční výchozí inicializaci, datové členy instance struktury budou inicializovány nulami. Získáme tedy nulový vektor, což dokládá následující fragment kódu:

Každá instance struktury disponuje svou vlastní kolekcí datových členů. My jsme zatím vyrobili pouze jeden vektor, jenž je uložen v hodnotové proměnné v1. Vytvořený vektor sdružuje tři datové členy, jež na fyzické úrovni reprezentují proměnné x, y a z. K datovým členům instance struktury přistupujeme prostřednictvím tečkového operátoru (.), a to tak, že nejdříve zapíšeme název strukturové proměnné, pak tečkový operátor a následně název požadovaného datového členu. Předcházející výpis kódu ukazuje, jak zjistíme složky vektoru: v1.x, v1.y a v1.z jsou ty správné výrazy.
Samozřejmě hodnoty datových členů instance struktury můžeme kdykoliv pozměnit. Připomeňme, že pokud není stanoveno jinak, dostaneme nulový vektor. To je vhodné v případě, jestliže je nulový vektor to, co potřebujeme. Jindy budeme chtít složky vektoru nakonfigurovat podle svých vlastních potřeb. To se děje kupříkladu v těchto přiřazovacích příkazech:

Na levé straně od operátoru přiřazení určujeme cílový datový člen, k němuž přistupujeme pomocí tečkové notace. Napravo je pak uvedena hodnota, kterou bude příslušný datový člen inicializován.
Generický model pro instanciaci struktury můžeme obměnit tak, aby využíval inferenční mechanizmus:

Zřejmou modifikací je substituce datového typu strukturové proměnné klíčovým slovem var. Samotná instanciace probíhá tak, jak jsme uvedli. Jakmile bude provedena analýza definičně-inicializačního příkazu, inferenční mechanizmus přisoudí strukturové proměnné konkrétní datový typ (X). 8 0463/CZ oPředstavenou trojici přiřazovacích příkazů mohou vývojáři v jazyce C# 3.0 nahradit jedním příkazem, v němž dochází k vícenásobnému přiřazení:

// Vícenásobné přiřazení.
v1.x = v1.y = v1.z = 1;

Podstatou vícenásobného přiřazení je inicializace n entit v jednom přiřazovacím příkazu, přičemž n > 1. Entity jsou separovány přiřazovacím operátorem. Po posledním výskytu operátoru = je zadána inicializační hodnota, která bude uložena do všech entit. Inicializace probíhá postupně, směrem zprava doleva. V ukázkovém příkazu je inicializační hodnota 1 v prvním kroku uložena do datového členu z. Poté je hodnota členu z přiřazena do členu y. Nakonec je hodnota členu y zkopírována do členu x. Všechny tři datové členy instance struktury tak budou mít shodný obsah (a společně budou formovat jednotkový 3D vektor).
Kompilátor jazyka C# 3.0 automaticky vkládá do těl deklarovaných struktur implicitní veřejně přístupný bezparametrický konstruktor. Jelikož tento konstruktor sestaví kompilátor sám, nemusíme jej vytvářet ve své vlastní režii (po pravdě řečeno, ani to není možné). Ačkoliv rozprava na téma „Konstruktory a struktury“ nás ještě jenom čeká, bude dobré, když si zapamatujete, že konstruktor je speciální člen struktury, jehož úkolem je inicializace instance struktury. V souvislosti s definičním příkazem je důležité zmínit následující: jazyk C# 3.0 umožňuje vytvářet více proměnných v jednom definičním příkazu. Podmínkou nicméně je, aby měly všechny definované proměnné identický datový typ. Je-li tomu tak, lze v rámci jednoho definičního příkazu definovat prakticky neomezený počet proměnných. Datový typ všech přítomných proměnných je uveden pouze jednou a identifikátory jednotlivých proměnných jsou odděleny čárkami.Inferenční mechanizmus má v programovacím jazyce C# 3.0 dalekosáhlejší praktické implikace, které se pojí zejména s využitím anonymních a generických datových typů. Jak budeme postupně více pronikat do tajů jazyka C# 3.0, s inferenčním mechanizmem se ještě mnohokrát setkáme.

Autor článku