👋 Nový obsah na borekb.cz

Info Tento blog je v "read-only módu" a nový obsah již nebude přibývat. O vývoji píšu na DevBlog.

Slabá podpora pozdní vazby v C#

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ě…

Zařazeno do kategorií |
enty (Út, 2006-04-18 06:28):

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…

Borek (Út, 2006-04-18 08:01):

2 enty: Děkuji za reakci, ale nemohu s vámi tak úplně souhlasit.

  • CLR dynamické jazyky a pozdní vazbu plně podporuje. Z čeho soudíte, že „z podstaty managed kodu je pozdni vazba prece vyloucena“?
  • Výhody pozdní vazby nejsou iluzorní, ale zcela reálné (viz příklad v článku). Jiná věc je, že pozdní vazba přináší také pár nevýhod.
  • C# pozdní vazbu podporuje, viz třeba příklad se zvířecí seznamkou. Podporuje ji ale nedostatečně.
  • Máte pravdu, že Reflection může být řešením, ale nevidím důvod, proč by lepší podpora pozdní vazby nemohla být zakomponována přímo do jazyka. VB.NET je dobrým příkladem.
Daniel Steigerwald (Út, 2006-04-18 09:39):

Čao Borku, síla C# je právě v tom, že normálním programováním (bez reflexe a generik) opravdu nemůžeš nechat na kompilátoru a CLR, aby si takhle volně za běhu programu dosazovali konkrétní typ objektu. Rozdíl mezi late a early vazbou neni ani tak o typovosti jazyka, jako o typovosti dané v compile time místo runtime. Celý C# je striktně typový a je to dobře. Opravdu to snižuje množství chyb (frázička :) generovaných za běhu programu :) Jenže co mě opravdu baví na C#, je jeho pružnost. Spousta problému s opakovaným kódem lze řešit generiky, nebo reflexí. Napříkad tvůj problém, bych řešil takhle:

protected void Page_Load(object sender, EventArgs e)
   {
       AttributeCollection col;
       foreach (Control c in this.Controls)
           if (c.GetType().GetProperty("Attributes") != null)
                col = (AttributeCollection)c.GetType().GetProperty("Attributes").GetValue(c, null);
   }

Jednoduché, ne?

Borek (Út, 2006-04-18 11:24):

2 Dan: V tom je právě ten paradox. Aby se v C# dala napsat stejná věc jako ve Visual Basicu, je potřeba použít Reflexi (nebo jmenný prostor Microsoft.Visu­alBasic.Compi­lerServices), což ale současně znamená ztrátu výhod statického typování. Otázkou pak zůstává, proč místo c.GetType().Get­Property(…).Get­Value(…) nepovolit zápis c.Attributes realizovaný pozdní vazbou.

dgx (Út, 2006-04-18 18:11):

Mnoho lidí si myslí, že brzká versus pozdní vazba úzce souvisí se staticky vs. dynamicky typovanými jazyky.

Co popisuješ souvisí čistě s rozdílem mezi staticky a dynamicky typovanými jazyky. Pozdní vazba je spíš vedlejším efektem.

Je trošku ironií, že hlavní nevýhodou staticky typovaných jazyků je to, že často programátoři vynakládají extra úsilí a píší více kódu jen proto, aby statické typování obešli.

Borek (Út, 2006-04-18 19:56):

Je trošku ironií, že hlavní nevýhodou staticky typovaných jazyků je to, že často programátoři vynakládají extra úsilí a píší více kódu jen proto, aby statické typování obešli.

Plně souhlasím. Před časem se mi ale stala i opačná věc: v PHP mi práci komplikovalo to, že se jazyk choval jako dynamicky typovaný a přitom jsem si na konkrétní typy objektů musel dávat setsakramenský pozor…

Komentáře jsou uzavřeny (blog je v read-only módu). Pokud mě chcete kontaktovat, můžete mailem.