Určitě jste o nich už slyšeli, možná i víte k čemu jsou a jak fungují. V tomto článku si je ale rozebereme trochu podrobněji.
Dalo by se říct, že konstruktor je metoda, která se zavolá vždy, když dojde k vytvoření instance třídy nebo struktury. V tomto článku se ale nebudu konstruktory struktur vůbec zabývat (struktura je hodnotový typ, při zavolání implicitního konstruktoru tak vznikne instance s vynulovanými datovými členy). Konstruktor může například nastavovat nějaké výchozí hodnoty, nebo je dostat jako parametr při vytváření instance třídy (pak mluvíme o parametrickém konstruktoru).
Základní konstruktor
public class MojeTřída
{
public MojeTřída()
{
// Toto je tělo konstruktoru.
}
// Zbytek třídy bude tady
}
Zatím je to nemastné neslané, máme třídu a v ní konstruktor, který nic nedělá. Jen si všimněte základního poznávacího prvku konstruktoru – musí se jmenovat naprosto stejně jako jeho třída. Jak už bylo několikrát zmíněno, k zavolání konstruktoru dojde při vytvoření instance třídy, tedy například takhle:
MojeTřída třída = new MojeTřída();
Přetěžování konstruktorů
C# podporuje tzv. přetěžování konstruktorů. To znamená, že konstruktorů jedné třídy máme víc, jen s jinými parametry. Naše třída MojeTřída se třemi konstruktory by vypadala takto:
public class MojeTřída
{
public MojeTřída()
{
// Bezparametrický konstruktor
// První konstruktor
}
public MojeTřída(int Věk)
{
// Konstruktor s jedním parametrem
// Druhý konstruktor
}
public MojeTřída(int Věk, string Jméno)
{
// Konstruktor se dvěma parametry
// Třetí konstruktor
}
// Zbytek třídy bude tady
}
Kompilátor si už sám vybere ten správný konstruktor podle parametrů, které zadáme instanci třídy. Opět si to ukážeme na příkladu.
MojeTřída třída1 = new MojeTřída();
// Použije se první, bezparametrický konstruktor
MojeTřída třída2 = new MojeTřída(20,"Honza");
// Tentokrát se použije třetí konstruktor
// přijímající dva parametry
MojeTřída třída3 = new MojeTřída("Honza");
// Při pokusu o zkompilování vyhodí chybu -
// nedefinovali jsme žádný konstruktor, který
// by přijímal jeden parametr typu string
Volání konstruktoru z jiného konstruktoru
C# nám umožňuje zavolat jeden konstruktor z jiného. Řekl bych, že příklad mluví za vše.
public class MojeTřída
{
public MojeTřída()
: this(10)
{
// Bezparametrický konstruktor
// Pvní konstruktor
}
public MojeTřída(int Věk)
{
// Konstruktor s jedním parametrem
// Druhý konstruktor
}
}
Trošku blíž si popíšeme tuto část:
public MojeTřída(): this(10)
Klíčové slovo this klasicky odkazuje na třídu, se kterou zrovna pracujeme. Nejinak je tomu tady. Když řekneme this(10), ve skutečnosti tím chceme zavolat metodu public MojeTřída(int Věk). Způsob, který jsme si právě předvedli, se nazývá inicializátor.
Další důležitou věcí je pořadí volání jednotlivých metod (konstruktorů), když využijeme inicializátor. Pokud vytvoříme instanci naší třídy s bezparametrickým konstruktorem, pak se nejdřív zavolá druhý konstruktor (ten s parametrem Věk). V C# nemůžeme explicitně zavolat konstruktor, jako je to možné například u metod, například že bychom napsali MojeTrida(20);. Jediný způsob jak zavolat konstruktor z konstruktoru tak zůstává zmíněná metoda přes inicializátor.
Malá poznámka na závěr: Jediná povolená klíčová slova v inicializátoru jsou this a base (to probereme v další kapitolce).
Konstruktory při dědění
Pro začátek si vytvoříme jednoduchou „třídovou“ konstrukci, ve které vytvoříme dceřinou třídu od třídy MojeBázováTřída.
public class MojeBázováTřída
{
public MojeBázováTřída()
{
// První konstruktor bázové třídy
}
public MojeBázováTřída(int Věk)
{
// Druhý konstruktor bázové třídy
}
}
public class MojeZděděnáTřída : MojeBázováTřída
// Zde dochází k dědění
{
public MojeZděděnáTřída()
{
// První konstruktor zděděné třídy
}
public MojeZděděnáTřída(int Věk)
: base(Věk)
{
// Druhý konstruktor zděděné třídy
}
}
Na výše uvedeném kódu není nic k nepochopení, ale jaké bude pořadí volání konstruktorů při vytvoření instance odvozené třídy? Jak mnozí už určitě tušíte, nejdříve se zavolá konstruktor bázové třídy a až po něm konstruktor jejího potomka (tedy odvozené třídy). Z toho tedy vyplývá, že kód konstruktoru bázové třídy se vykoná, i když ho explicitně nezavoláme (což ani nemůžeme) nebo i když nepoužijeme klíčové slovo base. Nelekejte se, to je naprosto normální chování konstruktorů při využití dědičnosti.
Privátní konstruktory
Privátní (soukromé) konstruktory, označené klíčovým slovem private, jsou trochu zvláštní skupinou, proto jim budu věnovat kousek článku. Je to kvůli tomu, že nemůžeme vytvářet instance nebo dědit od třídy, která má pouze privátní konstruktory. K čemu by nám taková třída byla? Téměř k ničemu, snad jen kdybychom ji nacpali statickými členy (opak instančního členu, který je vázán na instanci třídy, statický člen popisuje třídu jako takovou), tudíž bychom nechtěli vytvářet její instance. Můžeme ale mít vedle sebe jak soukromé, tak veřejné konstruktory (public). Vtip je v tom, že veřejný konstruktor se může pomocí nějakého zřetězení dostat do konstruktoru privátního, protože členy označené klíčovým slovem private jsou přeci dostupné zevnitř třídy. Pro lepší pochopení si to znovu ukážeme na příkladu:
public class MojeTřída
{
private MojeTřída()
{
Console.WriteLine("Toto je bezparametrický konstruktor (kl. slovo private!)");
}
public MojeTřída(int var)
: this()
{
Console.WriteLine("Toto je konstruktor s jedním parametrem (kl. slovo public)");
}
}
Pokud uděláme instanci třídy MojeTřída pomocí tohoto kódu:
MojeTřída obj = new MojeTřída(10);
vše bude fungovat jak má, i když se pomocí klíčového slova this odkazuje na privátní konstruktor. Kdybychom ale zkusili vytvořit instanci bez parametru, kompilace by skončila s chybovým hlášením 'Konstruktory.MojeTřída.MojeTřída()' is inaccessible due to its protection level. Tato hláška říká, že náš konstruktor je nepřístupný kvůli jeho přístupovému modifikátoru. Máme tedy před sebou jasný důkaz toho, že privátní členy jsou přístupné jen zevnitř třídy.
Můžeme vytvořit třídu, která bude mít jen privátní konstruktory, ale jak už jsem se zmínil, taková třída nebude děditelná, ani nebudeme moci vytvářet její instance.
To jsme probrali dědění, privátní konstruktory i nějaké obyčejné konstruktory. Co nám ještě zbývá? Dnes se podíváme už jen na statické konstruktory.
Statické konstruktory
Statické konstruktory jsou novinkou až v C# (tím myslím, že nebyly v C++, nevím, jak jsou na tom ostatní jazyky). Zajímavostí tohoto typu konstruktorů je to, že dojde k jeho zavolání ještě před vytvořením první instance třídy. Nemůžeme přesně říct, kdy dojde k zavolání; jediné, co víme jistě, je, že to bude ještě před instancí. Syntaxe je opět velmi jednoduchá, jen přidáme slůvko static.
public class MojeTřída
{
static MojeTřída()
{
// Tělo konstruktoru
// Může přistupovat jen ke statickým členům!
}
}
Základní charakteristiky statických konstruktorů si shrneme ve čtyřech bodech:
- V jedné třídě může být právě jeden statický konstruktor
- Takový konstruktor by měl být bezparametrický.
- Může přistupovat pouze ke statickým členům třídy.
- V hlavičce statického konstruktoru by neměl být žádný přístupový modifikátor.
Na statických konstruktorech toho mnoho zajímavého už nevykoukáme, takže je čas na nějaký příklad.
public class MojeTřída
{
static string jméno;
static MojeTřída()
{
jméno = "Honza";
}
}
Pokud zkusíte přečíst obsah proměnné jméno, klidně i před vytvořením instance třídy, bude v ní uloženo slovo Honza. A to proto, že došlo k zavolání konstruktoru hned, co se procesor dozvěděl, že máme s třídou něco v úmyslu.
FAQ
Na závěr dnešního článku si uvedeme takový krátký FAQ – často kladené otázky.
Co se stane, když nedefinuji žádný konstruktor?
Pokud programátor nedefinuje žádný explicitní konstruktor, kompilátor automaticky použije svůj bezparametrický. Pokud ale definujete třeba jen jeden konstruktor, potlačíte tím toto chování kompilátoru.
Co když budu mít konstruktor pro MojeZděděnáTřída, ale ne pro MojeBázováTřída?
Situace je podobná té výše, aplikace se bez potíží zkompiluje, kompilátor vygeneruje bezparametrický konstruktor pro bázovou třídu.
Může ne-statický konstruktor přistupovat ke statickým členům?
Ano, může, bez jakýchkoliv komplikací. Trošku přísnější jsou statické konstruktory, které mohou přistupovat jen ke statickým členům.
To je pro dnešek vše. Doufám, že vám článek řekl něco nového o světě konstruktorů v jazyce C#.