# Pokročilé programování v C\# ## Základní typování - typové konverze - když lze přiřadit proměnnou typu A do proměnné typu B? - referenční typy - pokud jsou typy kompatibilní z hlediska hierarchie dědičnosti → implicitní konverze - hodnotové typy - existuje implicitní konverze int → long - provádí se sign extension - podobně pro ostatní celočíselné typy (směrem, kterým se zachovává hodnota) - existuje i implicitní konverze na float nebo double – tam se ztrácí přesnost - neexistují konverze s boolem - hodnotový → referenční - provede se (implicitní) boxovací konverze - referenční typy – explicitní konverze - máme proměnnou typu object, chceme ji (explicitní konverzí) přiřadit do typu string - `string s = (string) o;` - je potřeba ověřit, že typ objektu uvnitř proměnné typu object je string (nebo jeho potomek) – tedy jestli se dá přiřadit - provádí se runtime check, aby se zjistilo, že se dá přiřadit - když to nejde, vyhodí se InvalidCastException - int a long od sebe nijak nedědí, pouze mezi nimi existují konverze - long → int je potřeba konvertovat explicitně (můžou se ztratit data) - unboxovat je potřeba explicitně (nemusí to vždycky fungovat) - musím přesně napsat typ, který je uvnitř (tzn. zaboxovaný int se nedá unboxovat do longu) - extension methods - uvažujme třídu Fraction (klasický zlomek, má intový čitatel a jmenovatel) - chci umět se zlomky pracovat třeba pomocí Math.Sin(…) apod. - hodilo by se implementovat konverzi na double - to by šlo zajistit metodou ToDouble() - tam potřebujeme inty dělit reálně, takže použijeme operátor explicitní konverze k implicitní konverzi - `public double ToDouble() => ((double) a) / b;` - hodilo by se implementovat konverzi z double na Fraction - mohli bychom mít statickou metodu ToFraction, která vrátí Fraction - Fraction.ToFraction … opakuje se tam slovo - tak by tam mohl být konstruktor `public Fraction (double d)` - to není úplně ideální - dědičnost? - nesmysl - chtěli bychom double.ToFraction - tomu se říká fluent syntax - použijeme extension metody - nová syntaxe - připomínka klíčového slova params – umožňuje metody s proměnným počtem parametrů - `class X { public static void f(this A a, int b) {…} }` - dá se volat `X.f(a1, 5);` - ale díky `this` se dá taky volat `a1.f(5);` - to se za překladu vyhodnotí jako `X.f(a1, 5);` - `this` se dá napsat pouze před první parametr - kdyby existovala i třída Y se stejnou metodou f, tak by se `a1.f(5);` nepřeložilo - třída X musí být statická (tzn. tohle this se dá zapsat jen u statické metody uvnitř statické třídy) - to urychluje compile-time hledání vhodné metody - vhodná metoda se hledá jen uvnitř aktuálního jmenného prostoru (a uvnitř jmenných prostorů importovaných pomocí `using`) - pokud volám `a.f(…)` - nejdřív se hledá vhodná metoda na typu proměnné - pak se hledají extension metody v konkrétním namespacu - pak se hledají extension metody v dalších namespaces - třída X by se měla jmenovat StringExtensions - extension metody se dají volat i na potomcích (tzn. hledají se tranzitivně extension metody všech předků) - pokud třída neimplementuje interface IComparable, tak nám extension metoda CompareTo nepomůže - k čemu se hodí extension metody - externí typy - obrovský projekt a malý podprojekt (do velkého projektu nechci sahat a přidávat tam pomocné metody / zvětšovat rozhraní jednotlivých tříd) - umožňuje nám to implementovat fluent syntax - method overloading - `m(int i)` a `m(long l)` spolu vůbec nesouvisí - v CIL kódu se objeví jako `m'int` a `m'long` - teda místo `int` tam bude `System.Int32` apod. - máme volání – za překladu se podle typu parametrů rozhodne, která metoda se bude volat - situace - metody v knihovně - volání v programu, který knihovnu používá - za překladu se určilo, že se volá `m'int` - ale v používané verzi knihovny je jenom `m'long` - JIT nutně zahlásí chybu – volaná metoda neexistuje - jak překladač vyhledává vhodný overload? - hledá v aktuálním kontextu - hledá se v kontextu metody a typu za překladu - tzn. mezi metodami, které jsou definované v daném typu - tudíž pokud se najde vhodná metoda definovaná uvnitř potomka, tak už se v předkovi nehledá - proč? kvůli tomu, že předek a potomek můžou být v různých assemblies (např. knihovna a hlavní program) - hledá podle arity - hledá nejspecifičtější overload + tak, aby to bylo nejméně práce - pokud mám metodu s overloady pro long, ValueType a object a volám ji pro int, tak se zavolá longová verze - dá se definovat uživatelská implicitní konverze - statická metoda `operator` v jednom z typů - parametr = zdrojový typ - název metody = cílový typ - lze zvolit, jestli je konverze implicitní nebo explicitní - způsob konverze probereme na cvičení - není dobré to s konverzemi přehánět - musí existovat jen jedna taková metoda – jinak překladač vyhodí chybu (ale až pokud chceme konverzi použít) - když bude jedna implicitní a druhá explicitní a pokusíme se provést explicitní konverzi, taky to vyhodí chybu (jelikož i implicitní konverzi lze volat explicitně) - když existuje (i uživatelsky definovaná) implicitní konverze na typ, pro který je definovaný overload, tak se použije ten (místo overloadu pro object) - dokonce to funguje i E2 –> E --> D –> A - kde –> je dědičnost - --> je uživatelská implicitní konverze - existuje overload pro A (ale pak už jen pro object) - pro proměnnou typu E2 se pustí overload pro A - ale vybírá se maximálně jedna uživatelská konverze - protože více konverzí vede ke zmatení - tedy na řetízku konverzí musí být maximálně jedna uživatelská, překladačových (implicitních hodnotových nebo dědičnostních) může být libovolně mnoho - zabudovaných konverzí se ale může vybrat víc - takže pokud na dané úrovni existuje jenom doublová varianta metody, tak se zavolá, pokud ji zavolám s parametrem typu char - protože existují implicitní konverze char → int → long → float → double - když se nic nenajde, tak se přesunu o kontext výš a opakuju kroky - situace - v předkovi A je metoda m(int) - v potomkovi B je metoda m(double) - chceme v potomkovi volat intový overload - ale `m(1);` volá `B.m(double);` - použijeme `((A) this).m(1);` - nemůžeme použít `base.m(1);`? - někdy ano, ale tohle vynucuje nevirtuální volání, což někdy nechceme - když se overloady (např. pro int a long) neliší jen typem, ale také sémantikou (třeba efektivitou apod.), tak je vhodné je pojmenovat různě ## Generiky ### Generické metody - chceme metody `int Max(int, int)` a `long Max(long, long)` - mohli bychom je rozkopírovat, protože vlastně vypadají úplně stejně - ale *kopírování je častým zdrojem chyb* - mohli bychom mít `object Max(object, object)`? - hodnotové typy by se musely boxovat :( - co když funkci najednou předám int a double? co má vrátit? - použijeme generické metody! - `T Max(T a, T b) { … }` - uvnitř můžeme používat typ `T` jako placeholder - metoda se používá jako `Max(…);` apod. - v C# se dá použít `Max<>(…);`, ale to má velmi specifické použití - v C++ - hlavičkový soubor - ve výsledném souboru nikdy není původní šablona - máme k dispozici jenom konkrétní specializace šablony, které jsme se rozhodli použít - v C# je myšlenka hlavičkových souborů nahrazena metadaty v assembly - generická metoda zůstává generickou na úrovni CIL kódu - tudíž CIL kód musí být dostatečně obecný – k tomu se dostaneme později - v CIL kódu volání bude výběr konkrétní specializace generické metody - JIT za run-timu vyrábí specializované varianty generické metody - konvence – placeholder typy obvykle začínají písmenem T - důležitá myšlenka – překladač si může vhodnou specializaci zvolit sám - takže napíšu `Max(…)` a překladač zvolí vhodnou specializaci automaticky - pokud má metoda např. tři parametry typů T1, T1, T2, tak překladač vlastně řeší rovnici - pokud se mi nelíbí automatická volba specializace a chci to ovlivnit, tak můžu přetypovat parametry – ale vhodnější je prostě definovat specializaci do špičatých závorek - generická metoda se dá kombinovat s konkrétními overloady - překladač zvolí overload pro konkrétní typy parametrů, pokud existuje - pokud neexistuje, typicky zvolí generickou metodu - situace – mám generickou metodu, která volá generickou metodu s konkrétními overloady - metoda `m` má generickou variantu, ale také variantu pro parametr typu double a pro object - `CallM` je generická, uvnitř je volání `m(v)`, kde `v` je typu `T` - to se přeloží na volání `m(v)` - konkrétní overloady se nikdy nezavolají, jelikož se za překladu musí určit, která jedna metoda se v rodičovské generické metodě bude volat - a generická `m` je prostě jediná vhodná – je použitelná pro všechna možná `T` - kdyby tam generická metoda nebyla, volal by se overload s parametrem object - tohle chování je jiné v C++ - tam se za generický typ dosazuje při překladu - připomenutí - máme generickou metodu - za compile timu vznikne jeden CIL kód této metody - za run timu – jakmile se zavolá určitá (např. intová) varianta metody, JIT vygeneruje strojový kód pro danou variantu metody - v CIL kódu je zapsáno, jaká metoda se volá - zda je to nějaká specializace generické metody - nebo je to třeba nějaký z konkrétních overloadů metody - ve složitější typové hierarchii - generická metoda může být virtuální - dá se overridovat pouze generickou metodou (jinou implementací) - pokud v potomkovi zadefinuju negenerickou metodu s dosud neexistujícím typem (tedy overload) s `new`, tak je to `new` zbytečné, nic se nezakrývá - co můžeme dělat uvnitř generické metody s parametrem typu `T`? - můžeme volat metody objectu - připomenutí - v Pythonu duck typing - v C# compile-time duck typing u pattern matchingu (viz dekonstruktory) - v C++ compile-time duck typing u šablon (generických metod) – strojový kód jednotlivých specializací metod se generuje za compile timu - v C# se taky dá používat pythonovský duck-typing - vydáme na půdu materiálního vulgarismu – použijeme klíčové slovo `dynamic` - u proměnné s typem `dynamic` se zapne runtime duck typing - proměnná se přeloží jako typ object - ale dají se na ní volat libovolné metody - za runtimu se zjistí, jestli existují – pokud ne, tak se vyhodí chyba - u generických metod použijeme interfaces, abychom mohli volat něco jiného než metody objectu - `void m(T a) where T : podmínky {}` - další where se píše na další řádek - tento způsob kontraktu je důležitý – i pro vývojáře daných metod (aby v další verzi něco nerozbili) - metoda s interfacovým parametrem vs. generická metoda s constraintem na daný interface - funguje to hodně podobně - rozdíl – interfaces u hodnotových typů - v metodě s interfacovým parametrem se bude hodnotový typ boxovat - v generické metodě se nic boxovat nebude, navíc se strojový kód vygeneruje přímo pro danou hodnotu (respektive pro daný typ) včetně všech optimalizací - ale typicky dává smysl preferovat klasickou metodu s interfacovým parametrem - JIT umí pro referenční typy recyklovat strojový kód - pro (různé) hodnotové typy to vždycky generuje nový strojový kód - u generických metod typicky chceme používat type inference (automatické generování špičatých závorek u volání) – to může komplikovat práci překladači (?) - další použití - generická rozhraní (interfaces) – např. `IComparable` - extension metody - `public static T[] Slice(this T[] source, …) { … }` - s generickými extension metodami je potřeba šetřit (a psát jasné constraints) ### Generické typy - může to být třída, struktura, interface - syntaxe podobná jako u metod – také s constraints - za compile timu se generuje jeden CIL kód generického typu - za runtimu JIT generuje typy jednotlivých specializací - strojový kód metod se klasicky JITuje až při prvním volání - každá specializace má svůj vlastní class constructor a své vlastní statické fieldy - dědičnost - specializace generických typů jsou z hlediska stromu dědičnosti na stejné úrovni (nevedou mezi nimi hrany) - generická třída může dědit od negenerické třídy nebo od generické třídy, kde její specializace může být daná nebo může odpovídat specializaci potomka - v generické třídě můžou být metody… - negenerické s určenými typy parametrů - negenerické s typy parametrů odpovídajícími specializaci generické třídy (tedy `T`) - generické (s typy parametrů nezávislých na specializaci generické třídy) - → překladač vždy vybere nejspecifičtější variantu - když má na výběr mezi negenerickou s určeným typem a negenerickou s typem T, tak vybere tu s určeným typem - `m(T t)`, kde `T` je `int` vs. `m(int i)` → vyhraje `m(int i)` - u constraints se dá použít čárka, ta znamená AND - OR by nemělo smysl - lze mít více generických typů s různými constraints - generické fieldy nejsou, ale můžu mít field typu T v generické třídě - interfaces - metoda interfacu se dá implementovat zděděním (rodičovský typ má metodu, kterou vynucuje interface) - jedna metoda může zároveň naplňovat více kontraktů (když třída implementuje víc interfaců a všechny požadují jednu metodu, např. `Close`) - interfaces: `IReader`, `IWriter` - metoda v obou interfaces se jmenuje `Close` - je potřeba rozmyslet, kdo volá `Close` (nebo `Dispose`) – kdo daný zdroj drží (a kdo je zodpovědný za jeho uzavření/uvolnění) - u TCP protokolu dává smysl mít dvě různé implementace `Close` - chceme mít oddělené zavření pro čtení a pro zápis - můžeme to poskládat pomocí dědičnosti – Reader implementuje IReader, Writer je potomkem Readera, zakrývá Close, implementuje IWriter, ReaderWriter je potomkem Writera, jeho Close zavolá obě varianty - ale je to hrozně složité řešení - explicitní implementace metody z interfacu - syntaxe - neuvedu viditelnost, za ni napíšu návratový typ, před jméno metody napíšu jméno interfacu, od názvu metody oddělím tečkou - https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/how-to-explicitly-implement-interface-members - takhle implementovanou metodu nemůžu zavolat přímo na daném typu, musím použít typ interfacu - takový přístup používá System.Int32, který implementuje interface IConvertible, ale interfacové metody to má implementované explicitně, takže se uživatelům běžně nezobrazují jako součást rozhraní - generické typy v kombinaci s interfaces - situace ze 4. prezentace - nepřeloží se – není jasné, metoda kterého interfacu se má volat - implementace generických interfaces - také v prezentaci - např. dvě vlastnosti se stejným jménem a odlišným typem nemůžou koexistovat, aspoň jednu z nich musíme implementovat explicitně - this - v metodách máme implicitní parametr this - to platí i pro metody v interfacech - metody v interfacech musí být instanční, jinak by nebylo jasné, co bude this - nová funkce C# – statické abstraktní metody v interfacech - je nezbytné je implementovat statickou metodou - k čemu je to užitečné? - když máme generický typ a chceme volat jeho statickou metodu - nově se u instančních metod v interfacu dá psát abstract (ale je lepší to tam nepsat) - funguje to i pro method-like věci (třeba vlastnosti) - do interfacu se dá zapsat defaultní implementace - this dává smysl používat jenom v instančních metodách - co když chci odkazovat na typ implementátora - použiju `TSelf` - `interface I1 where TSelf : I1 { … }` - kdy se tohle používá? - kdybych chtěl implementovat komplexní čísla s (generickým typem) volitelnou přesností - problém by nastal např. při implementaci sčítání - nově v C# existují interfaces, které nám tohle umožňují – mají implementovány potřebné operátory - speciální constrainty - constraint na hodnotové typy `where T : struct` - constraint na referenční typy `where T : class` ### Variance - `C` a `C` mezi sebou nemají žádnou vazbu – nijak od sebe nedědí - bylo by hezké, kdyby se `List` dal použít jako `List`, protože všechny stringy jsou potomky objectu - pojmy - $(\alpha)$ typ B je typově kompatibilní s A - typicky pokud B dědí od A - takže instance B se dá přiřadit do proměnné typu A - $(\beta)$ typ C je parametrizovaný T a `C` je typově kompatibilní s `C` - C je kovariantní podle T - $(\gamma)$ typ C je parametrizovaný T a `C` je typově kompatibilní s `C` - C je kontravariantní podle T - $(\delta)$ třetí varianta kompatibility `C` a `C` - C je invariantní dle T - generické typy jsou invariantní (??) - pole referenčních typů jsou kovariantní - pole hodnotových typů jsou invariantní - každý zápis do každého pole referenčních typů vede na runtime check - kdybych měl pole stringů - to přiřadil do proměnné typu pole objectů - a pak do prvku přiřadil int - tak by to bylo blbě, protože bych do pole stringů přiřadil int - i když kovarianci nepoužívám, check se provádí - kovariance - getter je v pohodě - setter je problém - kontravariance - setter je v pohodě - getter je problém - typ nemůže být zároveň kovariantní i kontravariantní - pro jiné typy než pole se dá zapnout variance – ukážeme si příště - od C# 9 jsou virtuální metody kovariantní dle návratové hodnoty - v overridu metody můžu vracet „lepší“ typ, než který vracela původně - ale tohle zase funguje jenom u referenčních typů - protože by si navrácené hodnoty neodpovídaly velikostně ani sémanticky (reference na haldu vs. číslo apod.) - kovariance u polí - někdy na škodu – musíme provádět runtime check - někdy užitečná – můžeme psát univerzálnější kód - specializace Listu jsou invariantní - takže do parametru typu `List` nemůžeme předat `List` - parametr `List` je zbytečně specifický, stačí nám vlastnost `Count` a možnost indexace - mohli bychom použít interface `IList` - takže můžeme jako parametr použít pole objectů - generické interfacy u referenčních typů jsou volitelně variantní - `interface I` je invariantní dle T - `interface I` je kovariantní dle T - funguje jako výstupní typ metody - `interface I` je kontravariantní dle T - funguje jako vstupní typ metody - pro každý typový parametr je to nezávislé - příklad - `interface I` - `I i = X : I` - E dědí od A - B = F - C dědí od G - D dědí od H - tady je to správně, v přednášce chybně, viz errata dokument - nestačí implicitní konverze – musí to být podle typového systému - neprovádějí se runtime checky, prostě se zakáže špatné použití - `IList` musí být zjevně invariantní, protože indexer vyžaduje getter a setter – tudíž musí být jako vstupní i výstupní typ - existuje `IReadonlyList`, kde indexer vyžaduje jenom getter - máme List stringů, chceme ho přiřadit do `IList`, to nejde - překladač nám poradí použít cast - s castem se to přeloží, ale runtime check selže - obecně se dá castovat typ do interfacu, který neimplementuje, jen se provádí runtime check, jestli konkrétní objekt (jeho typ) implementuje daný interface - v proměnné typu A mám instanci typu B - B dědí od A - typ A neimplementuje interface I, ale typ B ho implementuje - můžu tu proměnnou explicitně castnout na interface I - kdyby typ A byl sealed, tak by se nám cast na interface, který neimplementuje, ani nepřeložil - `interface IComparer` - `int Compare(T a, T b)` - kontravariantní - máme metodu s argumentem typu `IComparer` - `B` je potomkem `A` - takové metodě můžeme předat argument typu `IComparer` - kovarianci použijeme, když budeme chtít mít pro zvíře jeden logger - pro vlka chceme mít speciální logger uložený ve stejné proměnné ## Kolekce - interfacy kolekcí - dává smysl používat generické varianty – ty negenerické jsou tam kvůli zpětné kompatibilitě - IList má oproti ICollection navíc indexer - IEnumerable - kdyby IEnumerable měl vlastnost Current a metody MoveNext a Reset - seznam by byl immutable - ale backing field vlastnosti Current by byl mutable - takže by ten typ nebyl immutable jako celek - to je nepraktické při vícevláknovém programování - návrhový vzor iterátor - v dotnetu `IEnumerator` - dostaneme ho pomocí metody GetEnumerator - pak z každého vlákna můžeme kolekci procházet nezávisle - z dotnetu 1 je tam negenerický typ IEnumerable - vlastnost Current v negenerickém interfacu IEnumerator je typu object - takže při implementaci IEnumerable musíme implementovat obě metody GetEnumerator - a taky musíme implementovat oba iterátory - na `IEnumerable` spoléhá implementace foreach cyklu - metoda Reset je často k ničemu - mnoho iterátorů ji nemá - místo ní hážou výjimku (not implemented) - když budu implementovat iterátor, je lepší ho tam mít - když budu používat nějaký obecný iterátor, je lepší Reset nevolat – radši získat nový iterátor - namespaces (jmenné prostory) - můžu je vnořovat – je to syntaktická zkratka - tečka je v dotnetu validní součást identifikátoru - překladač vidí jenom názvy typů jako celek (i s tečkami) - v názvu jmenného prostoru může být tečka - takže namespace A.C je stejný jako namespace C uvnitř namespace A - jmenné prostory jsou z pohledu CLR ploché – zanoření nemá žádný speciální význam - když použijeme using, tak se aktivují extension metody daného jmenného prostoru - nested types (vnořené typy) - z pohledu CLR jsou typy opravdu vnořené (na rozdíl od namespaces) - mějme namespace X, v něm typ A, uvnitř vnořený typ B - konvence CLI pro názvy vnořených typů (pro výpis) - X.A+B - konvence C# pro názvy vnořených typů (pro použití v kódu) - X.A.B - k čemu je to užitečné? - v Javě je souvislost mezi instancemi A a B - uděláme instanci A - v kontextu A vyrobíme B - to B má zpětně referenci na instanci A - v C# tohle neplatí - v C# můžu lépe upravovat viditelnost typů - normální typy můžou mít viditelnost internal, public nebo file - vnořené typy můžou být private, protected, public, internal, … - docela častá je viditelnost private – daný typ vidí jenom kód uvnitř A - kdyby A byl SortedDictionary implementovaný červeno-černým stromem, tak potřebuju nějaký typ, který bude reprezentovat vrchol toho stromu - je to implementační detail, takže je vhodné, aby typ Node byl vnořený – takže nehrozí, aby ho začal používat někdo jiný - když máme metodu v internal interface a nějaká třída ji implementuje jako interfacovou, tak se bez přístupu k interfacu ta metoda nedá zavolat - metoda by taky mohla být interní a výsledek by byl stejný, ale to teď není důležité - podobně můžeme mít v nějakém typu vnořený private interface a použít podobný efekt u vnořeného typu, který ten interface implementuje - vnořený typ má přístup k private věcem nadřazeného typu - vnořený typ může dědit od nadřazeného typu - `IEnumerable` - dává smysl Enumerator mít jako privátní vnořenou třídu - jak se enumerátor chová - kolekce o 0 prvcích - pokud zavoláme getter vlastnosti .Current před spuštěním, vyhodí to InvalidOperationException - pokud zavoláme .MoveNext() u prázdné kolekce, vrátí to false - když potom zavoláme .Current, vyhodí to InvalidOperationException - kolekce o 1 prvku - .Current → InvalidOperationException - .MoveNext() → true - .Current → 1. prvek - .Current → 1. prvek - .MoveNext() → false - .Current → InvalidOperationException - enumerátor má tři stavy – před kolekcí, uvnitř kolekce, za kolekcí - metoda Reset by nás z libovolného stavu měla vrátit do stavu před kolekcí – ale často nebývá implementována - proč má enumerátor stav „před kolekcí“? aby podporoval prázdné kolekce - kdyby pro nás bylo důležité, kolik má kolekce prvků, použijeme ICollection s vlastností Count - nikdo nás nenutí projít IEnumerable celé, nikdo nás nenutí číst prvky (třeba nás jenom zajímá, jestli kolekce obsahuje nějaký prvek → jednou použijeme MoveNext) - v pokročilejších frameworcích může být uvnitř enumerátoru drženo spojení do databáze nebo nějaké cenné zdroje - IEnumerable rozšiřuje IDisposable - takže pokud používám enumerátor, měl bych nakonec zavolat Dispose - můžu vyrábět nekonečné kolekce – např. sekvenční generátor náhodných čísel - pokud potřebuju konečnou kolekci, použiju ICollection - concurrent modification - během používání enumerátoru modifikuju kolekci - může mi vzniknout race condition - tedy kolekce IEnumerable typicky nepodporují concurrent modification, může se vyhodit InvalidOperationException - problém je, když se modifikace provede mezi dvěma použitími jednoho enumerátoru - bug není v tom, že já modifikuju kolekci (pokud je třeba IList) - bug je v tom, že mi někdo dal moc velká práva ke kolekci - takže výjimka by měla být vyhozena z MoveNext - kolekce si může evidovat verze - enumerátor si může evidovat verzi, ze které vznikl - při volání MoveNext se porovná verze - foreach - generuje while cyklus - překladač… - nejdříve zkusí, jestli se na typu dá zavolat GetEnumerator - můžeme ji dodat i pomocí extension metody - tohle je vlastně duck-typing - pak zkouší, jestli typ implementuje generický interface - nakonec zkouší, jestli typ implementuje negenerický interface - na nalezený interface to přetypuje - dá se použít type inference (var) - pokud použijeme konkrétní typ, tak se návratová hodnota Current explicitně přetypuje na daný typ - jak pracovat s IList? - použít foreach nebo for cyklus? - při každém použití foreach se vytváří nový enumerátor - ale u polí se foreach překládá efektivněji – do for cyklu - pokud je za překladu jasné, že to bude pole - dává smysl, aby enumerátor byla struktura? - ale IEnumerable je interface, takže se enumerátor bude boxovat - no ale mohli bychom ve veřejné metodě GetEnumerator vracet strukturu - tím pádem musíme typ enumerátoru zveřejnit, aby se dal používat - tohle pak bude fungovat i s foreach cyklem - pokud překladač o typu ví jen to, že implementuje interface, tak na něm volá interfacovou metodu - programujeme enumerátor - máme třídu A, tam jsou dvě pole x1, x2, ale ta se mají tvářit jako jedno - kdybychom chtěli dělat PrintAll, pomocí for cyklu bychom přeiterovali přes obě pole – prolíná se algoritmus pro výpis a pro iteraci - v enumerátoru budeme mít uložený stav procházení - v jaké fázi jsme (které ze dvou polí zrovna procházíme) - v kolikátém jsme prvku - je vlastně docela těžké tvořit enumerátory - v C# je koncept iterátorových metod ### Iterátorové metody - pokud metoda vrací IEnumerator a obsahuje `yield return`, zcela se změní způsob jejího překladu - metoda se podle yield returnů rozseká na jednotlivé kroky - první krok – od začátku metody do návratové hodnoty yield returnu (včetně) - druhý krok – od středníku za yield returnem do dalšího yield returnu - poslední krok – od středníku za posledním yield returnem do konce metody - návratová hodnota yield return se někam uloží, takže volání getteru vlastnosti Current vrací přímo hodnotu (už ji znova nepočítá) - neplatná volání Current jsou v rozporu s typickým kontraktem enumerátoru - volání getteru Current před prvním MoveNext vrací defaultní hodnotu `default(T)` - get_Current po posledním MoveNext vrací poslední hodnotu - náš kód skončí uvnitř enumerátoru - v těle naší metody nezůstane náš kód, ale bude tam vyrobení a vrácení toho enumerátoru - lokální proměnné z našeho kódu budou uloženy jako fieldy enumerátoru - platí to pro všechny lokální proměnné - v Release režimu si překladač všimne, že některé lokální proměnné není třeba držet globálně a přeloží je jako lokální (ne jako fieldy) - parametry naší iterátorové metody se taky uloží do enumerátoru (respektive jejich hodnoty se tam uloží – „capture by value“) - pokud je iterátorová metoda instanční metoda nějakého objektu, tak v ní můžu použít vlastnosti toho objektu (protože má implicitní parametr this) - pak se musí `this` nakopírovat (capture by value) dovnitř enumerátoru - náš kód se přeloží do stavového automatu (metoda MoveNext funguje jako stavový automat) - `_state` - na začátku ve stavu 0 - při běhu se nastaví na -1 - až na konci se nastaví na další validní stav - tím způsobem se ošetřuje situace, kdy se při běhu MoveNext vyhodí výjimka – mohlo dojít k poškození vnitřního stavu enumerátoru, takže se prostě skončí - -1 je koncový stav - sdílený kód jednotlivých stavů se sdílí pomocí goto - můžeme používat `yield break` – to je okamžitý přechod do koncového stavu - enumerátor podporuje lazy evaluation - iterátorové metody taky – s každým voláním MoveNext se provede jenom jeden krok - chci IEnumerable převést na List - lazy evaluation se dá převést na eager evaluation - konstruktor Listu má overload `new List(IEnumerable)` - někdy se to hodí, někdy to není dobrý nápad - třeba pokud potřebuju jenom první tři položky, tak není vhodné převádět celou kolekci na seznam, když má milion položek - ale pokud se při každém MoveNext něco stahuje ze sítě a chci přes kolekci iterovat víckrát, tak dává smysl si ji někam uložit pomocí eager evaluace - v System.LINQ jsou k tomu metody ToList a ToArray pro IEnumerable - trochu efektivnější je ToList, protože ToArray se pak musí kopírovat do pole, jelikož LINQ předem nezná délku IEnumerable - ale pokud je daná věc zároveň IReadOnlyCollection a tedy má Count, tak ho LINQ použije - LinkedList - veřejná třída LinkedListNode – jednotlivé krabičky s hodnotami, aby se dalo přidávat před ně, za ně apod. - `LinkedList` implementuje `IEnumerable` - bylo by hezké, kdyby se dalo enumerovat přes krabičky v LinkedListu - `LinkedList` by mohl implementovat `IEnumerable>`, ale pak by se ten enumerátor složitěji používal - takže přidáme extension metodu `AsNodeEnumerable` - můžeme použít iterátorovou metodu - `x = new A {1,2,3};` je syntaktická zkratka za `x = new A(); x.Add(1); x.Add(2); x.Add(3).` - používá se compile-time ducktyping - Add se dá doplnit jako extension metoda - LinkedListNode - vlastnost Value s getterem a setterem - vlastnost ValueRef vrací referenci přímo na hodnotu (má jenom getter), takže může zefektivnit práci s hodnotami uvnitř LinkedListu - přidáváme krabičky uvnitř foreache za aktuální krabičku - pokud budeme enumerovat lazy evaluací přes LinkedList, tak se to zacyklí - protože enumerátor typicky nepodporuje concurrent modification - pokud LinkedList převedeme pomocí eager evaluace na List, tak se to nezacyklí, ale zabere to dost paměti - nejefektivnější varianta bude taková, že budeme prvek přidávat za minulou krabičku - iterátorové metody - můžou vracet i IEnumerable - např. metoda `IEnumerable Range(int from, int to)` s yield returny uvnitř - rozpadne se to do dvou tříd - jedna $(\alpha)$ bude implementovat IEnumerable - budou tam captured params by value - bude tam metoda GetEnumerator - tam se vytvoří instance enumerátoru (tedy té druhé třídy) - dovnitř se nakopírují captured params - druhá $(\beta)$ bude implementovat IEnumerator - tam bude náš kód - budou tam captured local vars by move - bude tam state - IEnumerator nemá odkaz na první třídu, ale má vykopírované její zachycené lokální proměnné - velice typická situace – `foreach (var x in Range(1, 10))` - tzn. vytvořily by se instance dvou tříd a ty by se zahodily - tedy místo dvou oddělených tříd C# překladač reálně vygeneruje jenom jednu třídu $(\gamma)$, která implementuje IEnumerable i IEnumerator - sémanticky to funguje tak, jako to byly dvě oddělené třídy - další stav … -2 - pokud ještě enumerátor nikdo nepoužil - takže pokud se GetEnumerator zavolá podruhé, potřetí apod., tak se vytvoří nová instance třídy $\gamma$ (kontroluje se, jestli byl iterátor použitý a jestli se používá ze správného vlákna) - v C++ 20 jsou taky iterátorové metody - máme kód pro generování Fibonacciho čísel - chceme po ChatGPT 3.5, aby nám to přepsalo do C# - obecná poznámka: pozor, ChatGPT lže - nabídne nám async metodu – což nepotřebujeme - poprosíme o přepsání → úspěch - řekneme, ať nám napíše testovací metodu, která bude zachycovat výjimku, když je požadovaná sekvence moc dlouhá - ale ta metoda nefunguje, výjimka se nezachytí - prosíme o přepsání, ale to nikam nevede - problém je v tom, že se výjimka vyhazuje v MoveNext – protože v metodě pro získání instance IEnumerable se žádný náš kód neprovádí - ale výjimka by se neměla šířit z MoveNext - je to špatná implementace - potřebovali bychom, aby se výjimka vyšířila z metody FibSeq - řešením je přepsat to – tenhle check provádět „výš“ - mít neiterátorovou metodu s checkem, která volá iterátorovou metodu, pokud check projde - je fajn používat CLS compliant typy – místo uintu použít int - místo InvalidOperationException je lepší vyhodit ArgumentOutOfRangeException - ChatGPT nám vygeneruje hezkou dokumentaci ## Reflection - proces překladu - máme C# zdrojáky - přípona .cs - ty se přeloží do CIL kódu - přípona .dll - assembly - CIL kód a metadata - ten se JITuje do konkrétního strojového kódu - programy ildasm a ILSpy zkoumají metadata jiné assembly - Reflection - typ Type - když zavoláme `typeof(A)`, kde `A` je typ, nebo `x.GetType()`, kde `x` je proměnná - typ Assembly - statické metody - `Assembly.GetExecutingAssembly()` - `Assembly.GetCallingAssembly()` - `Assembly.GetEntryAssembly()` - můžu přistupovat i ke knihovnám, které používám - jakmile dostanu instanci Assembly, můžu zavolat GetTypes - GetType(string) vrátí instanci Type, pokud typ s daným jménem existuje - na typu Type existuje spousta užitečných metod, které vrací instance potomků abstraktní třídy MemberInfo - GetFields - GetMethods - GetConstructors - GetProperties - GetEvents - co s tím? - můžeme implementovat pokročilejší koncepty, než nám C# defaultně umožňuje - kdybychom chtěli pythonovský duck typing (bez klíčového slova dynamic – to se někdy nedá použít) - dvě nesouvisející třídy A a B, obě mají metodu Run - chceme metodu RunIt, která na libovolném typu zavolá metodu Run, pokud ji ten typ má - `MethodInfo? mi = o.GetType().GetMethod("Run");` - zásadní problém reflection – není to safe, může to vést k běhovým chybám - na typu MethodInfo existuje metoda Invoke, která má dva parametry – první je typu object („this“ parametr) a druhý je `params` pole objectů (ostatní parametry metody) - `if (mi is not null) mi.Invoke(o, null);` - můžu hledat konkrétní variantu metody s parametry určitých typů - druhý parametr GetMethod … `new Type[] { typeof(string), typeof(long) }` - metoda Invoke - boxuje hodnotové typy a pak je případně zase unboxuje, aby typy pasovaly s typy parametrů - Reflection se pokouší dělat implicitní konverze samostatně – třeba pokud voláme metodu s longovým parametrem, ale dáme jí int, tak se to pokouší konvertovat - u GetMethods můžu použít BindingFlags - problémy reflection - je pomalá – kvůli obecnosti, boxingu, konverzím apod. - není safe – může způsobovat běhové chyby - je potenciálně nebezpečná – dá se přistupovat k privátním metodám, fieldům apod. - serializace objektů - instance objektů reprezentují data - tato data chci převést do textového (XML, JSON) nebo binárního (ProtocolBuffers) formátu - JSON - JavaScript Object Notation - javascriptové objekty jsou prototype-based, nemají klasické typy - JSON i XML data ukládají ve formě stromu - různé serializační frameworky nám umožňují různé způsoby serizalizace - serializační frameworky obvykle zvládají serializovat DAGy - pokud k objektu A vedou z kořene dvě orientované cesty, tak se vytvoří jeho kopie - někdy se framework dá nastavit, aby nevytvářel kopie, ale označil si objekty nějakými identifikátory - takže při deserializaci se zachová původní tvar grafu objektů - v C# jsou serializátory - v namespacu `System.Text.Json` je `string JsonSerializer.Serialize(T root)` - používá Reflection - co serializovat? - serializace veřejných vlastností – výchozí nastavení ## Delegáti - motivační příklad - máme třídu B s binárním stromečkem, která implementuje `IEnumerable` - mohli bychom foreachem enumerovat přes podstromy a yield returnem vracet hodnoty - při prvním volání MoveNext se vygeneruje kaskáda (vlastně spoják) enumerátorů - pro každý uzel se vyrobí enumerátor - kód je hezký a krátký, ale je to hrozně neefektivní - takže to implementujeme nějak líp - pomocí enumerátoru se na strom můžeme dívat jako na pole - metoda IndexOf vrátí index prvku v tom pohledu - můžeme použít enumerátor, ale to vede na O(n) algoritmus - IndexOf zná implementační detaily, takže může hodnotu hledat nějak efektivněji, třeba i v O(log n) - máme třídu A - interně má odkaz na třídu B - chceme najít prvek, který se rovná 5 - použijeme b.IndexOf(5) - chceme najít prvek, který je menší než 5 - musíme do třídy B dopsat IndexLessThan - to, co třída A potřebuje, vnucuje třídě B, že to musí umět - ale přitom jsme mohli napsat v třídě A klasický foreach s podmínkou – to by nebylo tak efektivní - chceme efektivitu i specifičnost - foreach - řídí to třída A - subkroky dělá třída B (MoveNext) - mohli bychom tu logiku otočit - v třídě B by byl cyklus - používal by kontrolu z třídy A - tzn. třída B by to řídila a subkroky by dělala třída A - chtěli bychom rozhraní IComparison s metodou Compare(int x) → bool - kde se ten interface má implementovat? - třída A by ho mohla implementovat - ale z implementačního detailu jsme ho dodali jako veřejné API třídy A - takže to nechceme - dovnitř bychom mohli dodat privátní třídu LessThan5, která by ho implementovala - to by šlo, ale je to hrozně ukecané - bylo by fajn, kdybychom to mohli udělat syntakticky úsporněji - my prostě chceme zavolat metodu - zatím neumíme předat ukazatel na metodu - v C to jde, ale není tam typová kontrola - v C# je koncept `delegate` (delegát) - je to typově bezpečné - `delegate` - klíčové slovo - každý delegát je referenčního typu - vždycky musíme deklarovat konkrétní typ delegáta - např. `delegate int D(string s);` - každý konkrétní delegát je potomkem System.MulticastDelegate (viz ET přednáška), ten je potomkem System.Delegate, ten je potomkem System.Object - funguje to jako libovolný jiný typ - default hodnota proměnné je null - `d1 = new D(m);` - kde m je identifikátor metody - nesmíme tam napsat m() – musí tam být proměnná typu D - obsahuje ukazatel na metodu - delegáti mají metodu Invoke - pozor, neplést s Invoke v reflection – tam je to výrazně obecnější (a pomalejší) - na jménech parametrů v deklaraci delegáta nezáleží – je to dokumentace - ale je možné „volat“ přímo delegáta – je to syntaktická zkratka pro Invoke - různí delegáti, kteří mají stejné parametry a stejný návratový typ, spolu nesouvisejí – nedají se navzájem přiřazovat - pozor - `D d = new D(m);` - `D d = m;` - je to skoro to samé - syntaxe s new vynucuje nového delegáta - druhá varianta používá cache – takže nemusím delegáta vyrábět znovu, pokud už existuje - obvykle můžeme použít druhou variantu - ale pokud instanci delegáta někde používám jako klíč, tak chci použít verzi s new - instance delegátů jsou immutable (podobně jako stringy) - delegáti a viditelnost - z vnějšího kontextu nemůžu vyrobit delegáta privátní statické metody - ale můžu mít public metodu, která vrací delegáta, který ukazuje na private statickou metodu - delegáti můžou ukazovat i na instanční metody - instanční metody mají jeden skrytý parametr – ale bylo by divné o nich uvažovat jako o víceparametrických než jsou - v delegátu jsou dva ukazatele – na funkci a na `this` - u statických metod je tam null - u instančních metod ukazuje na tu instanci - dají se dělat pokročilejší věci – viz ET přednáška - stále platí, že delegáti jsou immutable – to konkrétní `this` je tam navždy - u struktur – do `this` se zkopíruje struktura (zaboxuje se) - delegáti můžou být generičtí - můžou být kovariantní a kontravariantní – viz ET přednáška - u delegátů se dává suffix Delegate - občas mi jde čistě o parametry – je mi úplně jedno, co ten delegát dělá - bylo by zbytečné pro každou takovou metodu deklarovat delegáta - proto existuje spousta předdefinovaných delegátů - `Action<…>(…) → void` - `Func<…, TResult>(…) → TResult` - `Predicate(T obj) → bool` - pozor na přehlednost – typicky je lepší používat silně typované delegáty - `var` funguje i pro delegáty - `var d1 = new D(m);` - od C# 8 natural types - `var d1 = m;` - C# ví o delegátech ve standardní knihovně - vybere se nejlepší match z Action, Func - efektivita delegátů - výroba delegáta něco stojí - volání delegáta vyjde téměř nastejno - delegát má vlastnost Method - vrací MethodInfo - používá to reflection - je to celkem drahé - Invoke v reflection je výrazně pomalejší - vlastnost Method se cachuje - na MethodInfo je metoda CreateDelegate, abychom nemuseli používat Invoke z reflection – pak se dá volat efektivněji - reflection přiřazení do statických fieldů je taky pomalé - JSON serializer - ten v System.Text.Json serializuje veřejné vlastnosti - dá se aktivovat serializace privátních fieldů - pomocí delegáta - metoda Deserialize - návratový typ odpovídá očekávanému typu kořene - v JSONu objekty nejsou typované - při deserializaci se používá duck typing - může se to hodit k deep cloningu objektů - na každém objectu je protected metoda MemberwiseClone, která vrací mělkou kopii - když serializujeme nějaký objekt a pak ho deserializujeme, tak vlastně dostaneme hlubokou kopii (pokud to uděláme dobře) - tady se může hodit serializace privátných fieldů - defaultní je serializace veřejného kontraktu, aby nemohl záškodník aplikaci rozbít úpravami JSONu (pokud máme validaci v setterech) - coroutine - iterátorová metoda je příkladem coroutiny - umožňuje nám popsat algoritmus v krocích - např. v nějaké hře by pomocí kroků coroutine mohly být popsané kroky NPCčka - když serializujeme objekt enumerátoru (včetně privátních fieldů), tak máme serializovaný stav - ale je to závislé na implementačních detailech ### Lambda funkce - zpátky s motivačnímu příkladu - máme metodu FindIndex, která bere delegáta (predikát) - pokud predikát vrátí true, tak to vrátí ten index - někdy není moc praktické mít pomocné metody vypsané jako statické ve třídě - chtěli bychom je deklarovat v místě kódu, kde se používají - obvykle se tomu říká lambda funkce, my je budeme chápat jako anonymní funkce (mají nějaké jméno vygenerované C# překladačem) - napíšeme `static`, pak do závorky parametry, pak šipku `=>` a potom tělo funkce - dá se přímo zapsat jako parametr metody FindIndex - pozor na šipku – u normálních (pojmenovaných) funkcí je to syntaktický cukr - proč je tam klíčové slovo static? - označuje, že lambda funkce nemá stav - dříve se dalo použít slovo delegate – funguje to podobně (ale chybí tomu nějaké další funkce, které lambda funkce mají) - ekvivalentní příklady lambda funkcí - `static (int x) => { return x + 1; }` - `static (int x) => x + 1` - `static x => x + 1` - `static x => { return x + 1; }` - v posledních dvou příkladech se použije type inference - pokud je parametrů více, musejí tam být vždycky kulaté závorky - lambda funkce může být bez parametrů, pak se napíšou prázdné kulaté závorky - lambda funkce nejsou first-class entities – neexistuje proměnná typu „lambda funkce“ - jsou přiřaditelné do proměnných typu delegát (a ještě někam jinam – viz ET přednáška) - podle delegáta, kam to přiřazuju, funguje type inference parametrů - když někam přiřadím úplně stejnou lambda funkci, tak C# překladač může recyklovat vygenerovaný kód - může se stát, že voláme generickou metodu a předáváme ji jako parametr lambda funkci, takže překladač musí řešit trochu složitější rovnici - když to nevede na jednoznačné řešení, tak je to překladová chyba - když tam to static nedáme, tak se zapíná nějaká speciální funkce, ale pokud ji nepoužíváme, tak je to jedno - to static tam píšeme jenom proto, abychom tu speciální fíčuru nepoužili omylem - pozor, když nějakou lambda funkci používám často a rozmyslím se, že je užitečná, tak může dávat smysl mít ji tam jako klasickou statickou metodu - kde lambda funkce použít? - chceme nad kolekcí dat provést transformaci - tohle se dělá často v relačních databázích - databázová tabulka je vlastně kolekce řádků – chceme najít nějakou podmnožinu, setřídit to apod. - tyhle věci se typicky popisují pomocí SQL queries ### LINQ - language integrated queries - nějakou syntaxí podobnou SQL dotazům bych mohl popsat, co se má stát - jak je to doopravdy? - v C# je LINQ pouze syntaxe, nedefinuje to žádnou sémantiku - zatímco SQL dotazy mají i sémantiku - syntax LINQ dotazů - `from` x `in` data source, pak nějaké klauzule - můžu si to představit jako nějaký foreach (ale takhle to nefunguje – je to jenom představa) - klauzule má dvě části – nějaké klíčové slovo a kód - klíčové slovo se přeloží na volání vhodné metody, která má jako parametr lambda funkci - kód se předá jako tělo té lambda funkce - parametr lambda funkce je to X - má to výjimku – když to končí klauzulí select, která vrací jenom X, tak se ta klauzule nepřekládá - `from c in customers where c.City == "London" select c.Name` se přeloží na `customers.Where(c => c.City == "London").Select(c => c.Name)` - C# definuje jenom přeložení téhle syntaxe, to je celé - pokud se ta „C# syntaxe“ přeloží, tak je LINQ dotaz syntakticky správně, pokud se nepřeloží, tak je špatně, to je vše - to X (respektive v příkladu je to `c`) nemá žádný důležitý význam, jenom nám to umožňuje odkazovat se na parametr lambda funkce - pokud je těch funkcí víc, tak si můžeme představit, že ta jednotlivá X spolu vůbec nesouvisí - dotaz musí začínat from, vždycky tam musí být in a vždycky musí končit selectem – pokud vrací rovnou X, tak se to ignoruje - celý ten LINQový výraz má nějakou hodnotu – ta odpovídá návratové hodnotě posledního výrazu (může to být cokoliv – jakýkoliv typ) - fakt tam není sémantika - můžu mít metodu OrderBy, která zabíjí příšery a vrací počet těch, které jsme zabili - Select funguje podobně jako všechny ostatní klauzule – až na tu výjimku s ignorováním „prázdného“ selectu - LINQ funguje na principu duck typingu - může se stát, že nějaký typ nepodporuje určitou klauzuli – podpora se dá dodat extension metodami - v C# je definován základní LINQ to Objects - statická třída Enumerable – tam je přehršel generických extension metod, které extendují `IEnumerable` - ta třída je definovaná v `System.Linq` - dělá to ty operace, co bychom čekali - pozor, extension metoda je jenom fallback - kdybychom měli typ, který implementuje `IEnumerable`, ale byla by tam např. metoda Select, tak bude mít přednost před tou LINQovou - myšlenka LINQ to Objects – zavolání metod má jenom připravit dotaz, nemá se to reálně dotazovat - když zavolám Where, tak z ní vypadne krabička, kde je delegát a datový zdroj - ta krabička se jmenuje třeba WhereEnumerable - WhereEnumerable taky implementuje všechny metody z LINQu - ale tady už se nepoužívají extension metody - pokud zavolám Where na WhereEnumerable, tak si to nové WhereEnumerable bude jako zdroj pamatovat to původní WhereEnumerable - výsledek LINQ to Objects dotazu vytvoří vázaný seznam těch krabiček - všechny ty krabičky implementují rozhraní IEnumerable - můžeme na tom udělat foreach cyklus - při inicializaci dostaneme enumerátor poslední krabičky - první volání MoveNext - vygeneruje enumerátory všech krabiček v tom spojáku - zkontroluje to platnost predikátů u Where klauzulí - volá to MoveNext na enumerátorech, dokud nejsou predikáty splněny - LINQ to Objects provádí líné vyhodnocení (lazy evaluation) dotazu - ten dotaz nemusím enumerovat celý - jeden dotaz můžu spouštět vícekrát (od začátku) - dotazy můžu skládat - pozor, když mám proměnnou se seznamem, nad kterou postavím LINQ krabičky, a do této proměnné uložím jiný seznam, tak se dotaz nepřegeneruje – má v sobě uložený odkaz na entitu, ne na proměnnou - lazy evaluace se používá, pokud je to možné - pro některé operátory se dělá eager evaluace - Where … lazy - OrderBy … eager - ale to se týká jenom LINQ to Objects, neplatí to univerzálně - u eager evaluace se data musí někam uložit - C# překladač nedělá žádné optimalizace – je potřeba přemýšlet nad pořadím klauzulí - where → orderBy vs. orderBy → where - nejspíš bude efektivnější ta první varianta (pokud where a orderBy fungují tak, jak bychom čekali) - poznámka – LINQ to Objects dělá nějaké optimalizace - pokud máme dvě Where za sebou, tak se to sloučí do jedné krabičky, která má seznam podmínek - podobně Where a Select krabičky se můžou sloučit - ale nemění to fungování ### Lambda funkce se stavem - dosud nám k vyhodnocení funkce stačily její parametry - co kdybychom uvnitř funkce chtěli používat nějaké další proměnné? - když nepoužívám lambda funkce, ale předávám delegáta na instanční metodu – uvnitř delegáta se uloží (odkaz na) this - lambda funkci si můžeme představit jako funkci, která žije v nějakém kontextu - parametry – deklarace lokálních proměnných - tělo – deklarace nějakých proměnných a použití nějakých proměnných - použité proměnné můžou být 1. vázané (tzn. někde uvnitř funkce jsou deklarované) nebo 2. volné (nejsou deklarované uvnitř funkce) - některé jazyky umožňují mít funkci s volnými proměnnými jako first-class entity – ty se pak dodefinují před použitím - tohle v C# nelze - `static` zakazuje volné proměnné - za překladu musí být jasné, co ty volné proměnné znamenají (v daném kontextu) – převádějí se na vázané - lambda funkce bez volných proměnných = closure - v C# vlastně lambda funkce neexistují, jsou tam jen lambda výrazy a do delegátů se vždy přiřazují closures - potřebovali bychom nějakým trikem dostat do lambda funkce přiřazené hodnoty těch volných proměnných - vezmeme nějaký její scope - v tom scopu zachytíme volné proměnné (Scope si můžeme představit jako nějakou třídu/objekt, kde ta lambda funkce je jeho instanční metoda) - podobný princip jako u iterátorových metod - v C++ to takhle funguje, ale v C# ne - syntax lambda funkcí v C++ - `[](parametry) {tělo}` - do hranatých závorek se dá napsat `=`, to znamená capture by value - capture by value má zjevně nevýhody v mnoha situacích - pokud v lambda funkci něco spočítáme, nemůžeme to dostat ven - C++ umožňuje capture by reference, kdy se do hranatých závorek dá `&` - všechny fieldy ve scopu budou reference na nějaké místo - tohle v C# nejde, protože tracking reference mají omezení kvůli živnotnosti - v C# se neprovádí capture by value ani by reference - budeme tomu říkat „capture by move“ - proměnná se „přesune“ do toho scopu - když lambda funkce použije nějakou proměnnou z kontextu, změní se způsob překladu veškerého kódu za lambda funkcí (v daném kontextu) - veškerá další práce s danou proměnnou se přepíše na přístup k proměnné uvnitř scopu – jako by to byla veřejná vlastnost (nebo field) nějakého objektu Scope - C# překladač opravdu vyrábí instanci nějaké třídy Scope - všechny lambda funkce v daném kontextu sdílejí stejnou instanci scopu - jedna instance scopu tedy vlastně odpovídá jednomu volání rodičovské funkce - do delegátu se předává „this“ ukazující na konkrétní instanci scopu - ty pomocné třídy se v C# nejmenují Scope, ale DisplayClass - tohle se generuje C# překladačem na úrovni CIL kódu, takže JIT o lambda funkcích ani closure nic neví - `Enumerable.Range(1, 10)` vrátí lazy enumerátor čísel od 1 do 10 - metoda `.ForEach(Action)` - respektive `Array.ForEach(T[], Action)` pro pole - scope - vzniká ne kvůli lambda funkci, ale kvůli proměnným v ní - třídě scopu se říká DisplayClass - jeden scope odpovídá jedné úrovni životnosti proměnných - mějme proměnné a, i, j ve vnějším kontextu - mějme proměnnou k ve vnitřním kontextu - mějme lambda funkci ve vnitřním kontextu, ta používá proměnné i, j, k - vznikne jedna instance DisplayClass, která bude obsahovat proměnné i, j, a druhá instance, která bude obsahovat k - proměnné se neukládají podle názvu – takže pokud mám uvnitř for cyklu nějakou proměnnou `int a`, tak ta se bere v každé iteraci samostatně - DisplayClass s nejkratší životností - ukazuje na ty nadřazené s delší životností - jako metodu obsahuje tělo lambda funkce (?) - víc lambda funkcí - na generování DisplayClasses se nic nemění - pozor na zachytávání proměnných v lambda funkci – abychom něco nezachytili omylem - když ve VS najedu na šipečku lambda funkce, tak se zobrazí zachycené proměnné - častý bug – myslím si, že se používá capture by value (ale hodnota se před voláním lambda funkce změní) - „capture by accident“ ## Vícevláknové programování - vlákno = posloupnost zavolání funkcí - proces v C# - entrypoint - CLR-entrypoint - JIT - Main() - f() - chceme provést dva algoritmy (A a B) - můžeme je pravidelně střídat, provádět je po malých kouscích - může se zdát, že to běží současně, přestože to běží concurrent - lidský mozek průměruje svět za 100 ms, takže kdyby se to střídalo takhle rychle, tak by se nám zdálo, že to běží současně - proč bychom to vůbec chtěli - jak funguje železnice - koleje, po kterých jezdí vlaky - na nich jsou odbočky – výhybky mají směr + a – - můžeme počítat nápravy, které vjedou do nějakého úseku (nebo z něj vyjedou), abychom mohli určit volnost nějaké výhybky (říká se tomu počítač náprav) - přestavení výhybky nějakou chvíli trvá - návěstidla („semafory“) říkají strojvedoucím, v jakém je úsek stavu (na silnici světla nefungují stoprocentně, na železnici je to navržené bezpečně) - zelená = můžeš jet, nic ti nehrozí - červená = rozhodně nejezdi - železniční zabezpečovací zařízení (stavidlo, signalbox) implementuje tuhletu logiku, určuje, jaké světlo má svítit - myšlenka – výpravčí staví cestu (route) pro konkrétní vlak - jdeme implementovat stavidlo - jednotka práce stavidla je stavba jedné cesty, výrobci tomu říkají „driver“ - chtěli bychom spustit dva drivery současně - stačilo by nám to concurrent – občas se čeká, až se výhybka dostane do koncového stavu - cooperative (kooperativně přepínaná vlákna) - coroutine – rozdělení algoritmu na kroky - pomocí yield return - výhody - máme kontrolu nad tím, co se kdy dělá - coroutine říká, kde jeden krok končí - problémy - aktivní čekání – furt to něco dělá, žereme procesorový čas daný pro jedno vlákno - metoda Thread.Sleep(čas v ms) zařídí pasivní čekání vlákna … pasivní čekání (parametr s časem odpovídá minimálnímu času čekání, může to být i víc) - nemůžu využít další procesorová jádra - třída Thread - ve jmenném prostoru System.Threading - defaultně každá instance Thread reprezentuje jedno vlákno (ale může to být i nějak jinak) - preemtivně přepínaná vlákna - mezi dvěma libovolnými instrukcemi strojového kódu může dojít k přepnutí - statická vlastnost Thread.CurrentThread - na instanci vlákna - ManagedThreadId … dotnetí identifikátor konkrétního vlákna (liší se od identifikátoru, který používá operační systém) - konstruktor - jeden overload – bere delegáta ThreadStart - druhý overload – bere delegáta ParametrizedThreadStart(object o) - v tom místě to vlákno nevznikne - na té instanci se dají nastavit různé vlastnosti - Priority - IsBackground (defaultně false) - celý proces (běh aplikace) skončí, až skončí všechna foreground vlákna (v takovém případě CLR zabije všechna background vlákna) - až voláním Start() vznikne vlákno, zařadí se do plánovací fronty OS a jakmile se uvolní procesor, začne běžet - dva overloady Start() a Start(object o) odpovídají dvěma overloadům konstruktoru - context switch - u kooperativního přepínání vláken – overhead je malý, jenom se vrátím z MoveNext jednoho algoritmu a pustím MoveNext druhého algoritmu - desítky taktů procesoru - u preemptivního přepínání vláken – procesor se musí přepnout z uživatelského do supervisor režimu - IRQ timer → CPU (supervisor) → kernel → plánovač → Yield - tisíce taktů procesoru - je potřeba rozmyslet, jestli to dává smysl - stav vlákna - Unstarted - Running (něco jako Running) … neznamená to Running v OS - může být v OS stavu ready-to-run - "Ended" - vlastnost IsAlive – když je false, tak vlákno doběhlo - v OS to odpovídá stavu terminated - čekáme na doběhnutí vlákna - nedává smysl napsat `while (t.IsAlive)` … vyrobili jsme aktivní čekání - mohli bychom použít semiaktivní čekání pomocí `Thread.Sleep` - vlákno přejde do OS stavu waiting / dotnet stavu WaitSleep - mohlo by se stát, že nás někdo předběhne a budeme čekat hrozně dlouho - trik – Thread.Sleep(0) - vzdáme se procesoru, rovnou se zařadíme do fronty - v C# se dá použít Thread.Yield(), je to to samé - ale budeme žrát hodně času procesoru – kvůli context switchi - správné řešení … `t.Join()` - jakmile vlákno `t` doběhne, tak se naše vlákno rozeběhne dál - datové struktury, které vlákno používá, nejsou celou dobu v konzistentním stavu - nechceme, aby jiné vlákno mohlo vidět vadný stav nějaké datové struktury - tohle se v kooperativním přepínání řeší snadno, prostě do té nekonzistentní oblasti nedáme yield returny - v preemptivním přepínání to není jednoduché, vede to k race conditions - můžeme zjistit, jestli datová struktura je thread-safe (nikdy není vidět rozbitý stav) - datové struktury v dotnetu typicky nejsou thread-safe - vlákna běží v jednom procesu - každé vlákno má vlastní volací zásobník - všechna vlákna sdílí jednu haldu - problémy - context switch je hrozně drahý - vyrobení vlákna je ještě dražší - v dotnetu je třída ThreadPool - vyrobí si nějaké rozumné množství vláken (worker threads), které pasivně čekají - je tam nějaký pool položek (workitems) - na začátku je prázdný - pomocí QueueUserWorkitem se tam dá přidat úloha - jako parametr bere delegáta WaitCallback s parametrem `object state` - není to pool, ale fronta - měli bychom to používat spíš na malé položky - když uvnitř vlákna použijeme Wait/Join, tak to bude zabírat vlákno a nebude ho moct použít jiný úkol - ale s tím se taky trochu počítá – počet vláken v poolu není konstantní - API threadpoolu je hodně základní, ale naštěstí nad ním existuje spoustu dalších API - metoda QueueUserWorkitem je asynchronní, hned se vrátí - ale často by se nám hodilo, abychom pokračovali až ve chvíli, kdy ta úložka skončí - můžeme použít třídu Parallel, kde je spousta užitečných metod – v základu fungují jako synchronní - metody Invoke, For a ForEach - For a ForEach nenarvou do threadpoolu všechno najednou, ale nějak rozumně to dávkují ### Futures & promises - příklad s hamburgery - je neefektivní začít stavět fastfood, až když mám hamburgery - použijeme koncept futures (viz studijní materiál Thud!/Buch!) - výrobce hamburgerů koupí budoucí vepřové, chovatel prasat koupí budoucí kukuřici apod. - v dotnetu koncept future reprezentovaný třídou `Task` - `T` … kukuřice - `Task` … budoucí kukuřice - readonly vlastnost Result – nejprve zablokuje vlákno pomocí Wait (pokud není k dispozici výsledek), pak vrátí Result, jakmile je dostupný - vlastnost IsCompleted - když chceme vytvořit metodu, která vrací budoucí kukuřici, tak za její jméno dáme Async - `Task` je referenčního typu, může být null, pokud nelze požadavek splnit (když se future ani nevytvoří) - `Task` je potomek třídy `Task` - `Task` si můžeme představit jako `Task` – i to je užitečné (příklad s traktoristou, který orá pole, chceme zjistit, jestli má hotovo) - jak paralelizovat sčítání výsledků dvou drahých metod - varianta 0: obě pustíme synchronně - varianta 1: jednu z nich pustíme asynchronně, druhou synchronně - varianta 2: obě pusíme asynchronně, sčítáme jejich `Result`s - varianta 3: použijeme Task.WaitAll, abychom zabránili zbytečným context switchům ve variantě 2 - další metoda – Task.WaitAny - jak získat `Task` - `Task.FromResult` vytvoří hotový Task - koncept promise - třída TaskCompletionSource - metoda SetResult (striktně writeonly) - ke každému TaskCompletionSource existuje Task (ve vlastnosti Task) - defaultování futures (když slib nedokážu naplnit) - vlastnost IsCanceled (jedno $\ell$, protože americká angličtina) na Tasku - IsCompleted je pak taky true, ale existuje vlastnost IsCompletedSuccessfully - metoda SetCanceled na TaskCompletionSource - Task.Result na defaultnuté future vyvolá OperationCancelledException uvnitř AggregateException (ve vlastnosti InnerExceptions) - co se stane, když se z delegáta v threadpoolu vyšíří výjimka - nestane se nic, úložka je jakoby v try-catch bloku - když máme úložku, o které víme, že může spadnout, tak ji chceme mít v try bloku a v catchi popsat, co se má stát (chceme zachytávat Exception – tedy libovolné výjimky) - kromě IsCanceled může být future taky ve stavu IsFaulted, k tomu je metoda SetException (tu můžeme zavolat právě z toho catch bloku) - struktura CancellationToken - cooperative cancelling - abychom mohli operaci zrušit v nějakém rozumném místě (nechceme, abychom vytváření prasat zrušili ve chvíli, kdy nějakému praseti chybí noha) - readonly vlastnost IsCancellationRequested - přerušitelné async metody berou cancellation token jako parametr - je dobré ho předávat podřízeným metodám - je dobré ho kontrolovat hned na začátku metody - je potřeba ho kontrolovat ve všech místech, kde se dá metoda přerušit - přerušená metoda by neměla vrátit null, ale cancelled future - ale cancellation token se předává hodnotou – jak můžeme proces zrušit? - je tam třída CancellationTokenSource - funguje jako promise cancel requestu - vlastnost Token vrací CancellationToken - struktura CancellationToken obsahuje private referenci na CancellationTokenSource - na instanci CancellationTokenSource můžeme zavolat metodu Cancel - tenhle přístup striktně odděluje readonly CancellationToken od writeonly CancellationTokenSource ### Thread safety - máme dvě vlákna, která používají jednu datovou strukturu - část, kdy nějaké vlákno pracuje se sdílenou datovou strukturou = kritická sekce - chceme zabránit race condition - nejjednoduší přístup – mutual exclusion - použijeme zámky - v dotnetu – syncblock - to je jakoby ten zámek - každý objekt na GC haldě má v overheadu referenci na Type a referenci na syncblock - syncblock má dvě půlky: lock + „něco“ - lock (zámek) - je tam reference na vlákno, které drží ten zámek - defaultně je tam null - kvůli rekurzivnímu zamykání je tam počítadlo, kolikrát to vlákno ten zámek zamklo - je tam fronta čekajících vláken, které čekají na odemčení - jak zámek zamknout - je tam třída `Monitor.Enter(object o)` - při vytvoření objektu syncblock vůbec neexistuje, na haldě je tam null - při prvním zamčení objektu se vytvoří syncblock a zamkne se (atomicky) - když to samé vlákno zavolá Monitor.Enter, tak se zvýší Count - když jiné vlákno udělá Monitor.Enter, tak se zablokuje pasivním čekáním - jak zámek odemknout - `Monitor.Exit(object o)` - sníží se Count a pokud je nula, zámek se odemkne - pokud někdo čeká, předá se objekt čekajícímu vláknu (to si ho zamkne) - pokud nikdo nečeká, tak se reference na syncblock nastaví na null a syncblock se zařadí do poolu volných syncblocků, aby se daly použít u jiných objektů - pozor na zamykání hodnotových typů – Monitor.Exit ho typicky zaboxuje do jiného typu, než jsme zamkli - můžeme použít syntaktickou zkratku `lock (object o) { … }` - začátek složených závorek odpovídá Enteru, konec Exitu - kdybychom chtěli zamknout A, zamknout B, odemknout A a odemknout B, tak tuhle syntaxi použít nelze - zamyká to objekt, ne proměnnou - když v kritické sekci (v lock bloku) do proměnné přiřadíme nový objekt, tak se to může rozbít - lock blok bude i v takové situaci fungovat správně – odemkne se zamčený objekt - lock blok má Exit jakoby ve finally – odemkne se, i když se vyšíří výjimka - někdy to ale není to, co chceme – datovou strukturu typicky zamykáme, protože během zamčení není v konzistentním stavu - pokud bychom použili prostou kombinaci Monitor.Enter a Monitor.Exit bez finally, vyšířením výjimky mezi těmito dvěma příkazy by mohl nastat deadlock, pokud by stejný objekt chtěl zamknout někdo jiný - deadlocku si zákazník hned všimne, protože se mu aplikace zasekne - naopak nekonzistentního stavu datové struktury si všimnout nemusí - musíme zvážit, co je pro nás vhodnější - co když chceme zamknout proměnnou - vytvoříme si pomocnou proměnnou `object dataLock = new object();` - to je ta „esence zámku“ - pak si s proměnnou `data` můžeme dělat, co chceme – třeba do ní přiřadit nový objekt ### Asynchronní metody - asynchronní metoda (vol. 1) - na konci názvu bude typicky mít `Async` - vrací „future“ … `Task` - ten má Result, Exception, Status - hotovou future lze vytvořit pomocí `.From` - můžeme vrátit „promise“ … `TaskCompletionSource` - někdy by se nám hodilo něco, co by zahrnovalo dohromady future i promise - mělo by to delegáta na odpovídající úlohu, která vrací `T` - tohle se dá udělat právě tak, že napíšu `new Task(úloha)` - na tasku je metoda `Start`, která ho zafrontí do thread poolu - jako další parametr konstruktoru Tasku můžeme uvést cancellation token, pak Task umí detekovat, když se z něj vyšíří OperationCanceledException pro daný token a nastaví se na Canceled - užitečná věc: na cancellation tokenu existuje metoda ThrowIfCancellationRequested - když chci vytvořený Task rovnou pustit, napíšu `Task.Factory.StartNew(úloha)` - chci znát aktuální Task - `t = Task.Factory.StartNew(() => { … t … });` - tohle se někdy může rozbít – `t` může být null - je tam race condition – může se stát, že se úloha nejdřív zařadí do thread poolu a než se provede přiřazení do `t`, tak dojde ke kontext switchi → v `t` bude `null` - `t = new Task(() => { … t … }); t.Start();` - tohle se nerozbije - `Task.Run(úloha)` - pustí task v thread poolu - pomocí StartNew nebo Start se úloha nemusí pustit v thread poolu (pokud to nenakonfiguruju pomocí parametrů) – více viz poslední přednáška - příklad s velkými a malými úlohami v thread poolu - pokud uvnitř velké úlohy budeme plánovat malou úlohu do thread poolu a takových velkých úloh budeme mít 100, tak může dojít k thread pool starvation – úlohy se začnou vykonávat až při 101 vláknech - řešením může být použití třídy Parallel - nebo můžeme použít metodu ContinueWith - voláním `Task t2 = t1.ContinueWith(prevTask => { … });` navěsíme task2 za task1 - metoda `Task.Factory.ContinueWhenAll` - dá se vyrobit future, která bude completed nejdříve po určitém čase … `Task.Delay(ms)` - dá se vyrobit future, která bude completed, jakmile budou všechny zadané tasky splněné … `Task.WhenAll` - podobná je `Task.WhenAny` – když bude aspoň jeden naplněný - na dokončení tasku můžeme počkat pomocí Wait (nebo WaitAll, WaitAny) - lokální metody - uvnitř metody můžu definovat metodu - chová se to jako lambda funkce - variable capture (hodnoty proměnných) závisí na místě použití - více v ET přednášce - cíl: napsat kód ve formátu „něco rychlého, něco pomalého, něco rychlého, něco pomalého, něco rychlého, …“, který se bude vykonávat postupně, ale nebude blokovat vlákno - můžu použít iterátorové metody na plánování asynchronních korutin - ale to by bylo hodně programování - v C# na to existují „asynchronní metody“ (vol. 2) s klíčovým slovem `async` - `async` - `async Task LoremIpsumAsync(…) { … }` - jednotlivé kroky asynchronní metody jsou reprezentovány klíčovým slovem `await` - `await` funguje podobně jako `yield return` - opět se konstruuje stavový automat - když jsou v metodě dva řádky s await, tak se rozdělí do 3 kroků – před prvním await (včetně toho řádku s await), mezi prvním a druhým await a za druhým await - první krok se vždycky provádí synchronně ve volající metodě - tam můžeme provést kontrolu, třeba že parametry jsou v pořádku - ale neměli bychom tam provádět nic náročného – pokud nechceme blokovat volající metodu - `yield break` … konec korutiny - paralelním konceptem v `async` metodě je `return` - za `await` se píše objekt typu `Task` - ten se vrátí plánovacímu kódu, aby věděl, kdy má naplánovat další krok - await vrací `U` - za `return` se píše objekt typu `T` - to `return` tam fakt někde musí být, abychom věděli, co má být výsledek - i lambda funkce může být asynchronní - pozor na šíření výjimek - když se vyšíří výjimka z `async` metody f2Async - nastaví se na faulted - stejně se to chová, i když se výjimka vyšíří ze synchronního (prvního) kroku - kdybychom tam chtěli vyšířit klasickou výjimku, můžeme přidat mezikrok – klasickou asynchronní metodu - co když f2Async voláme z metody f1Async, aniž bychom četli výsledný Task? - nedozvíme se, že se vyšířila výjimka - await unwrapuje výjimky, respektive vezme první z nich a vyšíří ji ven - pozor na kombinaci zámku a awaitů - překladač nám zakáže psát `await` dovnitř `lock` bloku - `Monitor.Enter` a `Monitor.Exit` selže výjimkou - protože Exit voláme z jiného vlákna než Enter - jestli to fakt potřebujeme, tak musíme použít jiné synchronizační primitivum - semafor – má dvě implementace - Semaphore - potomek třídy WaitHandle - žádní potomci WaitHandle nejsou určeni k použití jako základní synchronizační primitivum, trvají hrozně dlouho - je to *meziprocesové* komunikační primitivum - SemaphoreSlim - ten chceme používat - má stejné vlastnosti jako syncblock - na začátku definujeme initial count (povolený počet zamčení) - vlastnost CurrentCount, metody Wait a Release - když je počítadlo na nule, tak metoda Wait zablokuje volajícího - jinak dekrementuje CurrentCount - Release inkrementuje CurrentCount - dá se nastavit i maximum count, abychom semafor neodemkli mockrát - není to omezené na vlákna - SemaphoreSlim(1) je něco jako zámek (s nerekurzivním zamykáním), akorát pro víc vláken (u zámku ho musí odemknout stejné vlákno) - je tam i metoda WaitAsync, která vrací Task označující, zda se to podařilo zamknout - další synchronizační primitiva - ReaderWriterLockSlim - readeři se navzájem nevylučují, writeři ano - přístup může mít v dané chvíli buď libovolně mnoho readerů, nebo jeden writer - CountdownEvent - něco jako semafor naopak - čeká se, dokud není hodnota nulová ### Monitor, threading model - promise, future - příklad: v jednom vlákně dáme task.Wait(), to vlákno se zablokuje - SetResult musíme provést v jiné vlákně - pak chceme opět pustit to zablokované vlákno - syncblock se skládá ze dvou částí - lock - owner thread - \+ counter (kvůli rekurzivnímu zamykání) - waiting threads list – nějaký férový seznam (nebo fronta, na tom nesejde) - condition variable - sleeping threads list - poznámka: zámku a podmínkové proměnné se dohromady říká monitor - třída Monitor - Monitor.Enter a Monitor.Exit obsluhují zámek - Monitor.Wait přidá vlákno do seznamu spících vláken – blokující volání - Monitor.Pulse probudí jedno ze seznamu spících vláken (nebo žádné, pokud je prázdný) – neblokující volání - Monitor.PulseAll probudí všechna vlákna - aby se dal použít Wait, Pulse nebo PulseAll, musí být obalený v lock bloku (pro stejný objekt) – jinak to vyhodí výjimku - kdyby to fungovalo přesně takhle, tak by tam byl deadlock - takže se Monitor.Wait chová trochu komplikovaně - nejprve to za mě udělá Monitor.Exit - pak nějaký InternalWait - tyhle dva kroky se dějí atomicky - pak se provede normální Monitor.Enter … blokující volání - proč musím držet ten zámek (proč se vynucuje) - mám nějakou podmínku - třeba boolovskou proměnnou - chceme vědět, kdy bude proměnná true - pokud neplatí podmínka, tak chceme čekat, až ji někdo splní - Monitor.Wait(x); - v jiném vlákně nastavíme proměnnou na true a dáme Monitor.Pulse - byla by tam race condition – proto tam musí být zámek - bez Monitor.Exit by tam byl deadlock - bez atomičnosti by se to mohlo rozbít - u Monitor.Pulse se zámek vynucuje kvůli tomu, že by mezi nastavením podmínky na true a Pulsem mohl někdo nastavit podmínku na false - ale ani takhle to ještě úplně nefunguje - místo `if (!condition)` tam musíme mít `while (!condition)` - ilustrace použití – problém producent/konzument (producer/consumer) - producent ukládá data do datové struktury - konzument data bere z datové struktury - dá se to snadno ladit – když je produkce rychlejší než konzumace, můžeme přidat konzumenty (nebo naopak) - jednoduchá situace - datová struktura … fronta - máme jednoho producenta a jednoho konzumenta - budeme používat Wait a Pulse - je důležité říct si v hlavě, co je ta podmínka - to je typická chyba - je fajn to explicitně napsat do komentáře - např. podmínka je „fronta je neprázdná“ - je dobré, aby pro konkrétní instanci objektu existovala jedna podmínka (aby po celou dobu existence znamenala to samé) - v producentovi – po přidání položky dáme Monitor.Pulse(queue) - v konzumentovi – `while (queue.Count == 0) Monitor.Wait(queue);` - dáme tam poison pill, abychom vlákna mohli ukončit, na konci programu vlákna vzbudíme všechna pomocí PulseAll - můžu použít i jiný objekt k zamykání - nechali jsme ChatGPT implementovat QueueWithConditions - příklad použití má po `queue.WaitUntilNotEmpty();` řádek s `queue.Dequeue();` - to vede k race condition - o podmínkových proměnných je potřeba dobře přemýšlet – jestli to nevede na race conditions nebo deadlocky - situace – funkce si chtějí mezi sebou předávat informaci, když běží ve stejném vlákně - můžeme použít `Thread.CurrentThread.ManagedThreadID` a mít slovník – ale to má nějaké nedostatky - tohle řeší operační systém – má thread local storage (TLS) - dotnet na to má wrapper - statickou proměnnou můžu označit atributem ThreadStatic - na každém místě, kde z ní čteme, se vygeneruje volání ReadTLS - kdekoliv do ní zapisujeme se vygeneruje WriteTLS - ale nestačí `static int a = 42;` - protože se to dá do statického konstruktoru, který se volá právě jednou z hlavního vlákna - takže pro ostatní vlákna tam bude nula - existuje třída ThreadLocal, kterou k tomu můžu použít - field nemusí být statický, ale to obvykle není dobrý nápad - pozor, jsou to drahé zdroje - různé frameworky definují threading model - pravidla, jak se mám k třídám chovat z více vláken - frameworky WinForms, WPF, MAUI, Avalonia a Uno mají podobný threading model - UI thread s nekonečnou smyčkou - když vznikne událost, tak se zařadí do fronty - vyřešení události se provede synchronně v UI threadu - nikdy se nemůže stát, že by běžely dvě události současně - přístup k prvkům uživatelskému rozhraní musí být z UI threadu - veškeré dlouhotrvající operace (nad 100 ms) bych měl dělat v jiném vlákně, aby nezamrzlo UI - jak dostat informaci o dokončení události do UI - v C# je koncept synchronizačního kontextu … abstraktní třída SynchronizationContext - každý framework si implementuje vlastní - metoda Post(úloha) … asynchronní - metoda Send(úloha) … synchronní, blokující - zachytíme (capture) sync context - chceme progress bar - krok těžkého zpracování → synchronizace → další krok → … - co když použijeme async metodu - vadilo by nám, kdyby se tasky plánovaly do thread poolu – blokovalo by nás to - Task.Start, Task.Factory.StartNew, ContinueWith a await se ptají na aktuální sync context – pokud není žádný, tak použijou thread pool - takže pokud tam je synchronizační kontext, tak se použije Post/Send - naopak Task.Run vždycky použije thread pool - když je tam těžkotonážní kód, který nechceme pustit konkurentně, ale opravdu paralelně, tak můžu napsat `await Task.Run` - metoda `SetSynchronizationContext` - metoda `ConfigureAwait(bool continueOnCapturedContext)` - ruší zachytávání synchronizačního kontextu, vynucuje threadpool - defaultní chování je s `true` parametrem - když tam dáme false, tak se ty věci budou volat vůči threadpoolu - nejtypičtější rada na internetu „dej tam ConfigureAwait(false)“ - ale je důležité vědět, co chceme udělat - můžeme takhle část kódu vytáhnout do vlákna threadpoolu a zbytek nechat v synchronizačním kontextu