Je to pár dní, co Microsoft uvolnil ke stažení Virtual PC Image s CTP Visual Studia 2010 a .NET Frameworkem 4.0. Hlavně mě zajímalo, co za novinky přinese nový C# 4.0 a tak se mi podařilo vygooglit tenhle docx a tuhle přednášku (zip ke stažení přejmenujte na pptx).
Hlavním přínosem jazyka C# 2.0 byly generiky, C# 3.0 přinesl LINQ a hlavním mottem C# 4.0 je dynamičnost. Sám Microsoft rozděluje novinky v C# 4.0 do několika základních skupin.
Dynamicky typované objekty
S příchodem jazyka C# 3.0 a jeho klíčovým slovem var se často (nesprávně) mluvilo o dynamickém typování. Ve skutečnosti byl typ var proměnné vyhodnocen už při kompilaci a výsledný kód byl plně statický (a veškerá typová kontrola se provedla během kompilace). C# 4.0 přináší klíčové slovo dynamic, které označuje, že typ proměnné je skutečně vyhodnocen až za běhu aplikace. Přesněji řečeno, kompilátor jen vytvoří všechny údaje o volání, které jsou potřebné za běhu aplikace pro skutečné vyvolání požadované operace dle aktuálně přiřazeného typu (bohužel tam zatím nejsou informace, které by umožnily použít extension metody). Můžeme tedy volat metody objektu typu dynamic, používat jeho properties, fieldy atd. Protože ale vývojové prostředí nemá žádnou informaci o dynamic typu (tedy ani jeho položkách), nemáme k dispozici IntelliSense ani kontrolu kódu. Za poznamenání stojí, že dynamické volání není provedeno jen při volání metod instancí typu dynamic, ale také při volání metod staticky typovaných proměnných, pokud jako parametr použijeme instanci typu dynamic. Za běhu se pak podle konkrétního typu v dynamic vybere vhodná varianta metody staticky typovaného objektu (a pokud není nalezena, vyletí nám výjimka).
Volání za běhu probíhá podle následujících podmínek: Pokud je dynamic objekt COM objekt, tak je volání odbaveno pomocí rozhraní IDispatch. Pokud dynamický objekt implementuje rozhraní IDynamicObject, pak je volání obslouženo metodou tohoto rozhraní. A pokud se jedná o obyčejný objekt, použije se reflexe na nalezení vhodné metody a invokuje se.
Volitelné parametry a parametry určené jménem
Volitelné parametry se specifikují tím způsobem, že se za jejich deklaraci uvede rovnítko a za něj defaultní hodnota, tj. ta hodnota, která se použije při neuvedení parametru.
Parametry určené jménem se používají tak, že se před hodnotu parametru uvede jméno parametru následované dvojtečkou, např. tedy Metoda(a: 5, c: 2, paramB: 1);. Zde je důležité si uvědomit, vyhodnocení parametrů proběhne v tom pořadí, v jakém jsme parametry zapsali my, a ne v pořadí formálních parametrů metody (tj. v jakém pořadí parametry uvedl autor metody).
Vylepšená COM interoperabilita
Vylepšení v této oblasti jsou přímočará a odstraňují zásadní nedostatky. COM metody často měly parametry a návratové hodnoty typu variant, který byl v prostředí .NET reprezentován datovým typem object. Nyní bude použit typ dynamic, což ušetří hromadu přetypování z objectu na konkrétní typ.
PIA (Primary Interop Assemblies) jsou skvělé během vývoje, protože nám umožňují pracovat nad nějakým COM API silně typově. To v C# 4.0 zůstává, ale je možnost nechat zapéct používané části z PIA přímo do assembly a za běhu je pak volání provedeno takovým způsobem, že nebudeme muset čelit problému s různými verzemi PIA. To se v praxi týkalo např. Office a VB.NET toto řešil už dříve (přes tzv. late binding), takže se často používal pro psaní wrapperů pro práci nad Office.
Dalším usnadněním práce je možnost vynechat u COM metod parametry předávané přes ref – pokud je vynecháme, tak kompilátor sám zajistí vytvoření nějakých dump objektů, které metodě předá a po návratu z metody zahodí.
Kovariance a kontravariance
Pro ty, co nevědí, co je kovariance a kontravariance, je tu pěkný článek na Wikipedii. Zjednodušeně řečeno, v typovém systému programovacího jazyka je unární operátor (který tedy převádí typ na typ) kovariantní, pokud zachovává hierarchičnost typů. To znamená, že pokud máme typ A a typ B (který je potomkem typu A), pak Op(B) je potomkem Op(A) (kde Op je unární kovariantní operátor z typu na typ).
Pokud operátor tuto hierarchii obrátí vzhůru nohama, mluvíme o kontravariantním operátoru. Pokud operátor není kovariantní ani kontravariantní, označujeme ho za invariantní.
Takovým unárním operátorem převádějícím typ na typ může být např. generika (vstupním typem je typový parametr, výstupním typem je generika pro vstupní typ). Vezměme IList<string> a IList<object>. Platí, že string je specializovanější object (je jeho potomek). Už ale neplatí, že IList<string> je potomek IList<object>, takže generika není kovariantní. Stejně tak IList<object> není potomkem IList<string>, takže generika není ani kontravariantní. Je tedy invariantní.
Za poznámku jistě stojí to, že s kovariancí a kontravariancí se setkáváme už od C# 2.0, kde návratové typy delegátů jsou kovariantní a parametry delegátů kontravariantní. Pokud máme delegáta s návratovým typem A, pak do něj můžeme přiřadit metodu vracející typ B (kde B je potomek A). A pokud máme delegáta s parametrem typu B, můžeme do něj přiřadit metodu s parametrem typu A.
Typové parametry generik
Zajímavým poznatkem je ale to, že pokud máme nějakou generiku, ve které bychom typový parametr použili jen pro výstup (tedy jako návratový typ metody – na typ out parametru to nefunguje), pak by taková generika byla kovariantní. Vezměme např. rozhraní IEnumerable<T>, které jen vrací prvky typu T (nelze prvky přidat). IEnumerable<object> nám pak bude vracet objekty typu object. Pokud místo IEnumerable<object> použijeme IEnumerable<string>, tak ale nemáme problém, protože nám bude vracet string, což je potomek objectu. Generika pracující pouze s výstupním typovým parametrem bude tedy kovariantní a lze v pohodě např. místo IEnumerable<object> použít IEnumerable<string>, protože druhý bude potomek prvého. V C# 4.0 je možné typový parametr generiky označit za výstupní tak, že před něj uvedeme klíčové slovo out a mezi třídami opravdu bude tento dědičný vztah.
A platí to i obráceně – pokud je nějaký typový parametr jen vstupní, je taková generika kontravariantní. Jako příklad se uvádí rozhraní IComparer<T> sloužící k porovnání instancí daného typu. Pokud máme IComparable<object>, tak to je schopné porovnat jakékoliv dva objecty. A pokud máme IComparer<string>, je schopné porovnat jakékoliv dva stringy. Stále platí, že string je potomkem objectu. Neplatí ale, že IComparer<string> by byl potomkem IComparer<object> – platí opačné – IComparer<object> je potomkem IComparer<string> a jedná se tedy o kontravarianci. Pokud totiž umíme porovnávat jakékoliv dva objekty, jistě umíme porovnávat i jakékoliv dva stringy. Opačná implikace ale neplatí! Pokud chceme nějaký typový parametr označit za vstupní, stačí před jeho jméno uvést nové klíčové slovo in.
Bohužel hodnotové typy jsou vždy považovány za invariantní (není mezi nimi ani vůči objectu žádná dědičná vazba), takže je nelze použít výše uvedeným způsobem.
Všechny standardní generické typy (IEnumerable, IComparer, IQueryable, Func, Action, Predicate, …) by měly být ve finální verzi .NET Frameworku 4.0 implementovány včetně in a out typových parametrů (v CTP zatím nejsou).
Na závěr…
Tak co si myslíte o novinkách v C# 4.0? Podle mě jako pěkné, ale mohlo toho být víc – např. indexed properties, něco z Davidových zbožných přání, podpora aspektů a la PostSharp nebo DBC (design by contract) podobně jako ve Spec#.
Ale zatím je venku jen CTP, tak uvidíme, co přinese čas, ale na moc novinek bych nesázel…
Na každou novou verzi C# se těším jako malý kluk :). Jsem nadšený hlavně z optional parametrů. Teď, když je venku první beta, plánuju si pořádně osahat taky dynamic. Myslím, že to přináší snadnou možnost integrování IronPythonu do aplikací jako skriptovacího jazyka. To je pro mě asi nejzajímavější použití dynamicu.
Jinak do .NET 4.0 prosáklo pár pěkných věcí ze Spec# (všechno důležité pro Design by Contract). Nejsem se si jistý, jestli to bylo už v CTP, každopádně v betě sedí v System.Diagnostics.Contracts statická třída Contract, pomocí které je možné definovat preconditions, postconditions a invarianty objektů.
To se mi líbíTo se mi líbí