Pokračuji v odhalování dalších “skrytých” vlastností jazyka. Předchozí díl najdete zde.
Duck Typing v .NET
Tato vlastnost známá především v prostředí dynamických jazyků si našla cestu i do .NET Frameworku. Vím zatím o dvou případech.
1) Máte vlastní typ reprezentující kolekci dat? Pak můžete jeho instanci vytvořit použitím inicializátoru kolekcí, který vyžaduje, aby daný typ implementoval rozhraní IEnumerable nebo jeho generickou verzi, což je pro kolekce vcelku běžné. Kromě toho už jen stačí mít veřejnou instanční metodu Add, přijímající libovolný počet parametrů libovolného typu (podle toho se také pak inicializátor používá).
class MyCollection : IEnumerable<string>
{
private List<string> data = new List<string>();
public void Add(string text)
{
data.Add(text);
}
public IEnumerator<string> GetEnumerator()
{
return data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// použití
var myCol = new MyCollection { "foo", "bar" };
Pro více parametrů (použití např. s Dictionary pro 2 parametry)
public void Add(string key, int value, bool overwrite = true)
{
if (data.ContainsKey(key)
{
if (!overwrite)
throw new ArgumentException();
else
data[key] = value;
}
else
data.Add(key, value);
}
// následující sice moc nedává smysl, ale ukazuje použití více parametrů
// metoda Add je samozřejmě použitelná nejen v tomto scénáři
var myCol = new MyCollection { { "foo", 1 }, { "foo", 2, false } };
2) Projetí kolekce pomocí foreach nevyžaduje, aby kolekce implementovala rozhraní IEnumerable nebo jeho generickou verzi. Stačí, když má veřejnou metodu GetEnumerator, která vrací typ X implementující IEnumerator nebo jeho generickou verzi. Ale vlastně ani to není podmínkou. Postačí, když tento typ X vystavuje veřejnou metodu bool MoveNext() a property Current libovolného typu (záleží na Vaší implementaci). Toto v .NET Frameworku existuje již od verze 1.0 a bylo přidáno proto, aby bylo možné procházet kolekce hodnotového typu bez boxingu.
Method Group
Možná jste se někdy dostali do následující situace
Console.WriteLine(10.ToString);
kdy zapomenuté závorky vyvolají poměrně kryptickou chybu při kompilaci: “Argument 1: cannot convert from 'method group' to 'bool'”. Nebo Vás na spojení “method group” přivedl ReSharper v rámci svých tipů a triků? Na tom moc nezáleží. Důležitá otázka je, co je to “method group”? Zde nám poradí například C# specifikace (§7.1 v C# 5.0 specs).
Method group je jméno pro skupinu metod, která je výsledkem tzv. “member lookup”, což je operace vyhledávající všechny kompatibilní členy pro nějakou akci (naše akce je Console.WriteLine). Tedy v našem případě je výsledkem této operace jméno ToString, které reprezentuje všechna nalezená přetížení metody ToString pro číslo 10. Mohou to být jak instanční metody, tak extension metody. Výsledkem této operace může být také jediný vhodný kandidát. Ze skupiny je takový vybrán na základě složitých pravidel, která jsem mírně nakousnul v článku Method Overloading.
Už tedy víme, co je to method group, ale stále nevíme, proč ji nejde podle chyby zkonvertovat třeba na bool. Zde pomůže sekce §6.6 Method group conversions, která říká, že method group lze zkonvertovat pouze na delegát odpovídajícího typu. Pravidla této konverze jsou tam také uvedena včetně následujícího příkladu. Pro více informací Vás proto odkazuji tam.
delegate string D1(object o);
delegate object D2(string s);
delegate object D3();
delegate string D4(object o, params object[] a);
delegate string D5(int i);
class Test
{
static string F(object o) {...}
static void G()
{
D1 d1 = F; // Ok
D2 d2 = F; // Ok
D3 d3 = F; // Error – nepoužitelné
D4 d4 = F; // Error – nepoužitelné v "normální formě"
D5 d5 = F; // Error – použitelné, ale nekompatibilní
}
}
Teď už tedy dokážeme zdůvodnit onu kompilační chybu a také víme, proč můžeme napsat následující.
bool Filter(int number)
{
return number > 5;
}
var nums = Enumerable.Range(1, 10);
var largerThanFive = nums.Where(Filter);
// namísto
var largerThanFive = nums.Where(i => Filter(i));
Double a decimal zaokrouhlování
double x = Math.Round(2.5); // 2
double y = Math.Round(1.5); // 2
Mě to takhle ve škole teda neučili. Vás ano? Pro vysvětlení jsem si došel na MSDN. Výsledná hodnota operace: “Číslo nejblíže vstupu. Pokud se zlomková část vstupu nachází uprostřed mezi dvěma čísly, pak přednost dostane sudé.” Tomuto se také říká “bankéřovo zaokrouhlování”, které je v souladu s normou IEEE. Je zavedeno za účelem minimalizace zaokrouhlovacích chyb při větším počtu zaokrouhlování. V .NET je používáno jako defaultní, ale lze jej změnit na to, které známe argumentem MidpointRounding.AwayFromZero.
BitArray
Aneb jak ušetřit na bool kolekci. Proměnná typu Boolean v .NET zabere v paměti 1 byte, což je 8x více, než je doopravdy potřeba. Pro uložení true/false nám stačí jediný bit, 0 nebo 1, a zbylých 7 zůstane nevyužito. Důvodem pro toto rozhodnutí je pohodlnější (nikoliv efektivnější) práce s pamětí uvnitř .NET. BitArray je kolekce bitů, která je interně reprezentována polem intů. Jediný Int32 zabere v paměti 4 byty a může tedy držet 32 bitů. Kromě úsporného řešení velkých booleovských kolekcí BitArray nabízí 4 základní bitové operace – And, Or, Not, Xor.
Rozdíly v podmíněných operátorech
Občas se setkám s tím, že někdo pro podmíněné operace AND a OR používá namísto podmíněných operátorů && a || logické operátory & a |. Nutno podotknout, že toto je možné pouze při práci s bool výrazy, jako třeba v if podmínce. Není to vyloženě špatně, ale skrývá to jeden podstatný rozdíl. Zatímco podmíněné operátory využívají tzv. “short circuit” princip, což znamená zkratování, logické operátory ne. Co to pro nás znamená? Např. v následujícím případě, pokud x = false, pak kompilátor ani nebude ověřovat hodnotu y, protože výraz už nemůže být true.
if (x && y) { ... }
Díky tomuto se nedostaneme do problémů, když napíšeme následující podmínku.
if (list != null && list.Count > 0) { ... }
Pokud by se zde druhá část výrazu prováděla a list by náhodou byl null, pak dostaneme NullReferenceException. A to je přesně to, co se stane, pokud budeme používat logické operátory tam, kde nemáme.