Polymorfismus je jedním ze základních konceptů objektově orientovaného programování. Na rozdíl od ostatních principů (zapouzdření, dědičnost, abstrakce apod.) se v případě polymorfismu jedná o poměrně komplikovanou záležitost (podívejte se třeba na Wikipedii), ale v podstatě se vše točí kolem jednoho pojmu, kterým je pozdní vazba.
Mým oblíbeným příkladem je zvířecí seznamka: objekt typu
Zvíře
má metodu představSe
, která sama
o sobě nevypisuje nic užitečného (např. „Jsem obecné
zvíře“). Odvozené třídy ale mohou s výhodou tuto metodu
překrýt a vypsat krátké představení sebe sama, např. „Jsem pes
Alík“ nebo „Jsem kočka Micka“. Metoda
PřidejDoSeznamky
pak může díky pozdní vazbě přijímat
obecný typ Zvíře
a spolehnout se, že volání metody
představSe
bude fungovat podle konkrétního zvířete a vypíše
se tedy smysluplná hláška namísto nicneříkajícího „Jsem obecné
zvíře“. Teoreticky řečeno, pozdní vazba (late binding) odkládá
rozhodnutí o konkrétní volané metodě až na dobu běhu, zatímco
brzká vazba (early binding) o volané metodě rozhoduje už při
kompilaci.
Mnoho lidí si myslí, že brzká versus pozdní vazba úzce souvisí se staticky vs. dynamicky typovanými jazyky. Je to pravda jen částečně, protože uvedený příklad se zvířátky bude stejně fungovat jak v PHP (dynamicky typovaném jazyku) tak v C# (staticky typovaném jazyku). Přesto však faktem zůstává, že v dynamicky typovaných jazycích hraje pozdní vazba větší roli než ve staticky typovaných jazycích.
Proč je pozdní vazba tak zajímavá? Protože je skvělá! Šetří mnoho práce a umožňuje jazyku chovat se intuitivně – pokud metodě očekávající Zvíře předám Kočku, intuitivně očekávám, že se s ní jako s Kočkou bude taky zacházet. Pozdní vazba je taky největším lákadlem stále se rozrůstající rodiny dynamicky typovaných jazyků (musím jmenovat PHP, Python, Ruby, MSH,…?).
Na druhou stranu ale mají i staticky typované jazyky své podstatné výhody: kompilátor může odchytit řadu chyb už při překladu, kód je o něco výkonnější, má lepší podporu ve vývojových prostředích apod. Teoretické debaty lze načíst třeba u Bruce Eckela nebo i jinde, z praktického pohledu je však důležité, že si Microsoft vybral cestu staticky typovaných jazyků. Ačkoliv tedy na .NET Frameworku běží už třeba PHP nebo Python, mainstreamový jazyk C# se kloní k odkazu C++ a Javy a používá statické typování, tedy pokud možno brzkou vazbu.
Což je škoda. Podívejte se, co v C# napsat nejde:
object helloObj;
helloObj = new HelloWorld();
helloObj.PrintHello("this fails!");
// error 'object' does not contain a definition for 'PrintHello'
Přitom ekvivalentní zápis ve Visual Basicu projde bez problémů. Dobře,
tohle byla jen malá ukázka, ale existuje taky zcela reálný příklad, kdy je
VB evidentně lepší než C#. Třídy HtmlControl a WebControl ze jmenného
prostoru System.Web.UI obsahují vlastnost Attributes
, která
obsahuje všechny atributy zadané v HTML zdroji, které nelze namapovat na
standardní vlastnosti typu Font nebo Enabled. Přímým předkem tříd
HtmlControl a WebControl je třída Control, která však prvek
Attributes
neobsahuje a zde vzniká problém: jak iterovat přes
všechny ovládací prvky daného elementu a např. testovat přítomnost
atributu MyKey?
První pokus
foreach (Control c in parent.Controls) {
if (c.Attributes["MyKey"] != null) {
doSomething(c);
}
}
samozřejmě neprojde, protože Control žádný prvek
Attributes
neobsahuje. Při správném použití pozdní vazby by
už ale následující kód měl fungovat:
foreach (Control c in parent.Controls) {
Control c1;
if (c is HtmlControl) { c1 = c as HtmlControl; }
else if (c is WebControl) { c1 = c as WebControl; }
if (c1 != null && c1.Attributes["MyKey"] != null)
{
doSomething(c1);
}
}
V C# však neprojde ani tento kód, protože ačkoliv je v c1 zaručeně instance HtmlControl nebo WebControl, díky slabé podpoře pozdní vazby si kompilátor myslí, že se snažíme vlastnost Attributes zavolat na instanci třídy Control. Ekvivalent ve VB je přitom opět úplně v pohodě (akorát je potřeba c1 definovat jako Object, nikoliv Control).
Jaké to nese pro vývojáře v C# důsledky? Poměrně nepříjemné, protože nyní se nelze vyhnout duplikování kódu – doSomething je potřeba zavolat dvakrát, poprvé v případě, že se jedná o HtmlControl, podruhé v přípědě WebControlu. V určitých případech může být řešením rozhraní IAttributeAccessor, které definuje metody GetAttribute a SetAttribute a dá se tak občas použít jako předek místo Controlu, ale v řadě případů ani toto rozhraní problém neřeší.
Před časem jsem proto zkusil založit feature request ‚Late binding in C#‘, ale C# týmem byl tento požadavek zamítnut „by design“.
Co z toho plyne? Vždycky jsem byl naivně přesvědčen, že jazyk nehraje roli a že záleží pouze na „frameworku“, ať už se tímto frameworkem myslí Delphi VCL, Java JCL, .NET BCL, PHP knihovny nebo cokoliv jiného. Jazyky jsem považoval za nahraditelné a více méně ekvivalentní, než jsem ale narazil na pozdní vazbu. Nejde jen o to, že v C# musím napsat o pár znaků víc (protože musím určovat typy), ale tento jazyk mi přímo brání dělat některé věci nejlepším způsobem, což má dopad na přehlednost kódu, jeho udržovatelnost apod.
C# mám rád, dobře se mi v něm pracuje, ale doufám, že podobných klacků pod mé nohy bude házet co nejméně. Tak jako tak, od této zkušenosti pokukuji po dynamických jazycích daleko vážněji. O Ruby napíšu někdy příště…
Tento clanek je bohuzel dukazem fatalniho nepochopeni C#, ktery nikdy nemuze pozdni vazbu podporovat. Z podstaty managed kodu je pozdni vazba prece vyloucena :-) Pokud tak trvate na iluzornich „vyhodach“ pozdni vazby, muzete vesele pouzit Reflection a dosahnete stejnych vysledku. A dokonce to mozna bude vykonnejsi…