typové konverze
string s = (string) o;
extension methods
public double ToDouble() => ((double) a) / b;
public Fraction (double d)
class X { public static void f(this A a, int b) {…} }
X.f(a1, 5);
this
se dá taky volat a1.f(5);
X.f(a1, 5);
this
se dá napsat pouze před první parametra1.f(5);
nepřeložilousing
)a.f(…)
method overloading
m(int i)
a m(long l)
spolu vůbec nesouvisím'int
a m'long
int
tam bude System.Int32
apod.m'int
m'long
operator
v jednom z typům(1);
volá B.m(double);
((A) this).m(1);
base.m(1);
?mohli bychom mít object Max(object, object)
?
T Max<T>(T a, T b) { … }
T
jako placeholderMax<int>(…);
apod.Max<>(…);
, ale to má velmi specifické použitív C++
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
důležitá myšlenka – překladač si může vhodnou specializaci zvolit sám
Max(…)
a překladač zvolí vhodnou specializaci automatickygenerická metoda se dá kombinovat s konkrétními overloady
situace – mám generickou metodu, která volá generickou metodu s konkrétními overloady
m
má generickou variantu, ale také variantu pro parametr typu double a pro objectCallM<T>
je generická, uvnitř je volání m(v)
, kde v
je typu T
m<T>(v)
m
je prostě jediná vhodná – je použitelná pro všechna možná T
v CIL kódu je zapsáno, jaká metoda se volá
ve složitější typové hierarchii
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
v C# se taky dá používat pythonovský duck-typing
dynamic
dynamic
se zapne runtime duck typingu generických metod použijeme interfaces, abychom mohli volat něco jiného než metody objectu
void m<T>(T a) where T : podmínky {}
metoda s interfacovým parametrem vs. generická metoda s constraintem na daný interface
další použití
IComparable<T>
public static T[] Slice<T>(this T[] source, …) { … }
dědičnost
v generické třídě můžou být metody…
T
)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
interfaces
Close
)IReader
, IWriter
Close
Close
(nebo Dispose
) – kdo daný zdroj drží (a kdo je zodpovědný za jeho uzavření/uvolnění)Close
explicitní implementace metody z interfacu
generické typy v kombinaci s interfaces
implementace generických interfaces
this
nová funkce C# – statické abstraktní metody v interfacech
TSelf
interface I1<TSelf> where TSelf : I1<TSelf> { … }
speciální constrainty
where T : struct
where T : class
pojmy
C<B>
je typově kompatibilní s C<A>
C<A>
je typově kompatibilní s C<B>
C<A>
a C<B>
každý zápis do každého pole referenčních typů vede na runtime check
kovariance
kontravariance
od C# 9 jsou virtuální metody kovariantní dle návratové hodnoty
kovariance u polí
specializace Listu jsou invariantní
takže do parametru typu List<object>
nemůžeme předat List<Person>
parametr List<object>
je zbytečně specifický, stačí nám vlastnost Count
a možnost indexace
IList<object>
generické interfacy u referenčních typů jsou volitelně variantní
interface I<T>
je invariantní dle Tinterface I<out T>
je kovariantní dle Tinterface I<in T>
je kontravariantní dle Tinterface I<out T1, T2, in T3, in T4>
I<A,B,C,D> i = X : I<E,F,G,H>
máme List stringů, chceme ho přiřadit do IList<object>
, to nejde
obecně se dá castovat typ do interfacu, který neimplementuje, jen se provádí runtime check, jestli konkrétní objekt (jeho typ) implementuje daný interface
interface IComparer<in T>
int Compare(T a, T b)
IComparer<B>
B
je potomkem A
IComparer<A>
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é
interfacy kolekcí
IEnumerator<out T>
IEnumerable
spoléhá implementace foreach cyklunamespaces (jmenné prostory)
nested types (vnořené typy)
IEnumerable<T>
jak pracovat s IList?
programujeme enumerátor
neplatná volání Current jsou v rozporu s typickým kontraktem enumerátoru
default(T)
lokální proměnné z našeho kódu budou uloženy jako fieldy enumerátoru
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
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
new List<T>(IEnumerable<T>)
LinkedList
LinkedList<T>
implementuje IEnumerable<T>
LinkedList<T>
by mohl implementovat IEnumerable<LinkedListNode<T>>
, ale pak by se ten enumerátor složitěji používalAsNodeEnumerable
x = new A {1,2,3};
je syntaktická zkratka za x = new A(); x.Add(1); x.Add(2); x.Add(3).
iterátorové metody
IEnumerable<T> Range(int from, int to)
s yield returny uvnitřforeach (var x in Range(1, 10))
v C++ 20 jsou taky iterátorové metody
proces překladu
typ Type
když zavoláme typeof(A)
, kde A
je typ, nebo x.GetType()
, kde x
je proměnná
typ Assembly
Assembly.GetExecutingAssembly()
Assembly.GetCallingAssembly()
Assembly.GetEntryAssembly()
na typu Type existuje spousta užitečných metod, které vrací instance potomků abstraktní třídy MemberInfo
co s tím?
MethodInfo? mi = o.GetType().GetMethod("Run");
params
pole objectů (ostatní parametry metody)if (mi is not null) mi.Invoke(o, null);
new Type[] { typeof(string), typeof(long) }
System.Text.Json
je string JsonSerializer.Serialize<T>(T root)
motivační příklad
IEnumerable<int>
delegate
(delegát)delegate
delegate int D(string s);
d1 = new D(m);
D d = new D(m);
D d = m;
delegáti a viditelnost
delegáti můžou ukazovat i na instanční metody
this
this
je tam navždythis
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
občas mi jde čistě o parametry – je mi úplně jedno, co ten delegát dělá
Action<…>(…) → void
Func<…, TResult>(…) → TResult
Predicate<T>(T obj) → bool
var
funguje i pro delegáty
var d1 = new D(m);
var d1 = m;
efektivita delegátů
JSON serializer
coroutine
zpátky s motivačnímu příkladu
napíšeme static
, pak do závorky parametry, pak šipku =>
a potom tělo funkce
dá se přímo zapsat jako parametr metody FindIndex
proč je tam klíčové slovo static?
označuje, že lambda funkce nemá stav
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; }
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)
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
kde lambda funkce použít?
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é klauzulefrom 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
) nemá žádný důležitý význam, jenom nám to umožňuje odkazovat se na parametr lambda funkcefakt tam není sémantika
můžu mít metodu OrderBy, která zabíjí příšery a vrací počet těch, které jsme zabili
v C# je definován základní LINQ to Objects
IEnumerable<T>
System.Linq
IEnumerable<T>
, ale byla by tam např. metoda Select, tak bude mít přednost před tou LINQovoulambda funkci si můžeme představit jako funkci, která žije v nějakém kontextu
static
zakazuje volné proměnnépotřebovali bychom nějakým trikem dostat do lambda funkce přiřazené hodnoty těch volných proměnných
[](parametry) {tělo}
=
, to znamená capture by value&
metoda .ForEach(Action<T>)
respektive Array.ForEach(T[], Action<T>)
pro pole
scope
int a
, tak ta se bere v každé iteraci samostatněproces v C#
chceme provést dva algoritmy (A a B)
proč bychom to vůbec chtěli
třída Thread
context switch
stav vlákna
while (t.IsAlive)
… vyrobili jsme aktivní čekáníThread.Sleep
t.Join()
t
doběhne, tak se naše vlákno rozeběhne dáldatové struktury, které vlákno používá, nejsou celou dobu v konzistentním stavu
problémy
v dotnetu je třída ThreadPool
object state
příklad s hamburgery
v dotnetu koncept future reprezentovaný třídou Task<T>
T
… kukuřiceTask<T>
… budoucí kukuřiceTask<T>
je potomek třídy Task
Task
si můžeme představit jako Task<void>
– 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
Result
sjak získat Task<T>
Task.FromResult<T>
vytvoří hotový Taskdefaultování futures (když slib nedokážu naplnit)
co se stane, když se z delegáta v threadpoolu vyšíří výjimka
struktura CancellationToken
část, kdy nějaké vlákno pracuje se sdílenou datovou strukturou = kritická sekce
v dotnetu – syncblock
lock (zámek)
Monitor.Enter(object o)
Monitor.Exit(object o)
lock (object o) { … }
object dataLock = new object();
data
můžeme dělat, co chceme – třeba do ní přiřadit nový objektasynchronní metoda (vol. 1)
Async
Task<T>
.From
TaskCompletionSource<T>
někdy by se nám hodilo něco, co by zahrnovalo dohromady future i promise
T
new Task<T>(úloha)
Start
, která ho zafrontí do thread pooluchci znát aktuální Task
t = Task.Factory.StartNew(() => { … t … });
t
může být nullt
, tak dojde ke kontext switchi → v t
bude null
t = new Task(() => { … t … }); t.Start();
Task.Run(úloha)
příklad s velkými a malými úlohami v thread poolu
Task t2<int> = t1.ContinueWith(prevTask => { … });
navěsíme task2 za task1Task.Factory.ContinueWhenAll
Task.Delay(ms)
Task.WhenAll
Task.WhenAny
– když bude aspoň jeden naplněnýlokální metody
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
async
async
async Task<T> LoremIpsumAsync(…) { … }
await
await
funguje podobně jako yield return
yield break
… konec korutinyasync
metodě je return
await
se píše objekt typu Task<U>
U
return
se píše objekt typu T
return
tam fakt někde musí být, abychom věděli, co má být výsledekpozor na šíření výjimek
async
metody f2Asyncpozor na kombinaci zámku a awaitů
await
dovnitř lock
blokuMonitor.Enter
a Monitor.Exit
selže výjimkousemafor – má dvě implementace
další synchronizační primitiva
promise, future
if (!condition)
tam musíme mít while (!condition)
ilustrace použití – problém producent/konzument (producer/consumer)
while (queue.Count == 0) Monitor.Wait(queue);
queue.WaitUntilNotEmpty();
řádek s queue.Dequeue();
situace – funkce si chtějí mezi sebou předávat informaci, když běží ve stejném vlákně
Thread.CurrentThread.ManagedThreadID
a mít slovník – ale to má nějaké nedostatkystatic int a = 42;
různé frameworky definují threading model
await Task.Run
SetSynchronizationContext
ConfigureAwait(bool continueOnCapturedContext)
true
parametrem