Date post: | 13-Apr-2017 |
Category: |
Education |
Upload: | jan-hridel |
View: | 202 times |
Download: | 0 times |
Jazyk C# Přednáška 03/04 – příkazy, operátory, OOP – třídy, vlastnosti, události
Myšlenka týdne
Zabalení (boxing) • Pokud je na místě, kde se očekává instance referenčního typu,
potřeba použít hodnotový typ (typicky předdefinované kolekce System.Collection), je potřeba zařídit převod.
• C# nabízí pro převod instance hodnotového typu na referenční typ operaci zabalení (boxing). Pro opačný převod pak vybalení (unboxing).
Zabalení (boxing) Pokud se přiřadí odkazu na typ object hodnota hodnotového typu, object o = 11;
vytvoří překladač instanci „obalové“ třídy, do které uloží hodnotu z pravé strany. Tuto hodnotu lze získat kdykoli zpět přetypováním int i = (int)o;
• Za běhu programu se kontroluje, zda má přetypování smysl. Jinak se vyvolává výjimka System.InvalidCastException
Příkazy • Hlavní rozdíly mezi C++ a C# V C# není příkaz asm.
Trochu odlišná pravidla platí pro switch a goto.
V C# má výrazový příkaz silnější pravidla.
V C# jsou příkazy navíc foreach, checked, unchecked, lock, using a yield.
Příkazy – základní konstrukce • Výrazový příkaz Vzniká připojením středníku na konec výrazu. Syntaxe
výraz ; Na rozdíl od C++ nelze použít libovolný výraz, ale pouze výraz, který má
vedlejší efekt.
A+B; 5;
Operátory ++, --, přiřazení, volání metody,
…
Příkazy – základní konstrukce • Blok Všude tam, kde lze použít příkaz, lze použít i složený příkaz – blok. Syntaxe
{ příkazynep } Libivolné příkazy včetně deklarací a
vnořených bloků.
Příkazy – základní konstrukce • Blok Na rozdíl od C++ nelze ve vnořeném bloku deklarovat stejný identifikátor
jako ve vnějším. { // Začátek vnějšího bloku int i = 5; { // Začátek vnitřního bloku int i = 6; } }
V C# chyba.
Příkazy – základní konstrukce • Deklarace Plnohodnotný příkaz – může se objevit kdekoli mezi ostatními příkazy.
Rozhodování • if Způsob použití je stejný jako v C++ Syntaxe
if (podmínka) příkaz1 if (podmínka) příkaz1 else příkaz2
podmínka – výraz typu bool nebo typu, který lze implicitně zkonvertovat na typ bool nebo na typ, který má operator true(). Na rozdíl od C++ zde nelze deklarovat proměnnou nebo použít čísla a reference.
Rozhodování • switch Syntaxe
switch (výraz) { alternativy }
alternativy: alternativa alternativa alternativy alternativa: návěští příkazy návěští: case konstanta : case konstanta : návěští default :
Rozhodování • switch Odlišnosti oproti C++
Jako konstanty v návěstích case lze použít i znakové řetězce a jako řídící výraz lze použít proměnnou typu string.
Program nesmí přejít automaticky z jedné alternativy do druhé – každá alternativa musí být ukončena break, goto, return nebo throw.
Program může přejít do jiné alternativy jen pomocí příkazu goto s uvedením návěstí.
Rozhodování • switch
Příklad: https://goo.gl/byJ9hf string s; while (true)
{
Console.WriteLine("Zadej text");
s = Console.ReadLine();
switch (s)
{
case "while":
case "switch":
Console.WriteLine("Zadal jsi klíčové slovo");
break;
case "konec":
return;
case "if":
goto case "while";
case "":
Cykly • V příkazu cyklu lze stejně jako v C++ používat příkazy break a continue.
• while Syntaxe
while(podmínka) příkaz
Odlišnosti oproti C++ jsou v podmínce – totéž jako u příkazu if
Cykly • for Syntaxe
for ( incializacenep ; podmínkanep ; kroknep ) příkaz
Zde lze deklarovat
proměnné. Lze uvést několik
výrazů oddělených
čárkou.
Platí totéž co pro podmínku příkazu if.
Jeden nebo skupina příkazů
oddělených čárkou.
Cykly • do-while Syntaxe
do příkaz while (podmínka)
Platí totéž co pro podmínku příkazu if.
Cykly • foreach Slouží k iteraci všech prvků v kolekci (kontejneru) Syntaxe
foreach(typ identifikátoru in výraz) příkaz
Musí určovat nějakou kolekci
Řídící proměnná cyklu. Deklarace
proměnné, do které se ukládají
jednotlivé hodnoty prvků z kolekce
Cykly • foreach Jako kolekci lze použít jakýkoli datový typ implementující rozhraní IEnumerable nebo jeho generickou obdobu, tedy typ obsahující veřejnou metodu GetEnumerator().
Jako kolekci lze též použít metodu či vlastnost obsahující iterátor.
Vrací hodnotu E, kterou lze použít jako enumerátor.
Cykly • foreach E implementuje Ienumerator, obsahuje tedy:
bool MoveNext()
Reset()
Current
Přejde na další prvek kolekce. Vrací false, pokud už další prvek v kolekci neexistuje.
Veřejná metoda. Nastaví enumerátor do výchozí pozice (před první prvek).
Vlastnost poskytující aktuální prvek kolekce.
Cykly • foreach Enumerátor může být jak referenčního, tak hodnotového typu. Průběh
• Vyhodnotí se výraz, tím se získá kolekce K. Má-li výraz hodnotu null, vyvolá se výjimka Syste.NullReferenceException.
• Pomocí K.GetEnumerator() se získá enumerátor E. Pokud je E referenčního typu a má hodnotu null, vyvolá se výjimka Syste.NullReferenceException
• Provádění cyklu skončí, když E.MoveNext() vrací false.
Skoky • jump statements – způsobují přenos řízení
• break, continue, goto, return a throw
Stejný jako v C++
Stejný jako v C++
Stejný jako v C++
Skoky • goto Syntaxe goto identifikátor ; goto default ; goto case konstanta ;
Stejný jako v C++
Pouze v těle příkazu switch.
Pouze v těle příkazu switch.
Skoky • Throw Význam stejný jako v C++ Slouží k vyvolání výjimky Syntaxe throw výraz ;
Odkaz na instanci třídy odvozené od System.Exception. Pokud je null, vyvolá
se System.NullReferenceException
Příkaz using • V C++ neexistuje
• Řídí životnost objektů, které zpravidla spravují resources (např. soubory)
• Syntaxe using (získání_prostředku) příkaz;
deklarace_lokální_proměnné výraz
Příkaz using • Prostředek – instance třídy nebo struktury, která
implementuje rozhraní System.IDisposable, které obsahuje metodu void Dispose().
• Deklarace lokální proměnné – deklarace proměnné s inicializací, která představuje prostředek.
• Výraz – výraz, jehož výsledkem je prostředek.
• Příkaz – příkaz, v němž se nesmí přiřadit jiná hodnota do proměnné deklarované v části získání prostředků.
Příkaz using • Příklad ekvivalentních konstrukcí
using (R r = new R()) { r.f(); }
R r = new R(); try { r.f(); } finally { if (r != null) ((IDisposable)r).Dispose(); }
Příkaz using • Je možné deklarovat i několik proměnných stejného typu
oddělených čárkou.
using (R r1 = new R(), r2 = new R()) { r1.f(); r2.f(); }
using (R r1 = new R()) { using (R r2 = new R()) { r1.f(); r2.f(); } }
Příkaz lock • Nemá v C++ obdobu. • Slouží k synchronizaci mezi podprocesy (threads) programu
pomocí vzájemně výlučných zámků – mutexů.
• Syntaxe lock (výraz) příkaz
• Příkaz lock získá pro daný objekt mutex, vyvolá příkaz a mutex uvolní.
Odkaz na referenční typ. Zde nedochází k zabalení
hodnotových typů.
Příkaz lock lock (x) { /* ... */ }
object obj = x; System.Threading.Monitor.Enter(obj); try { /* ... */ } finally { System.Threading.Monitor.Exit(obj); }
Přiřazení výrazu x do proměnné obj je zde proto, aby se výraz x
vyhodnotil pouze jednou.
Příkaz lock • Pro statické metody třídy se často jako zámek používá
instance třídy Type – získá se prostřednictvím operátoru typeof.
• Pro vyloučení současného volání přidej a odeber lze psát: class SynchronizovanýObjekt {
public static void Přidej(object x) { lock(typeof(SynchronizovanýObjekt)) { /* tělo metody */ } } public static void Odeber(object x) { lock(typeof(SynchronizovanýObjekt)) { /* tělo metody */ } }
}
Příkaz yield • V C++ neexistuje
• Používá se v iterátoru – viz dále.
Příkaz try • Podobně jako v C++ se jedná o pokusný blok Zachycení a ošetření výjimek – viz výjimky
Operátory a výrazy • Přehled operátorů Operátory se ve výrazu vyhodnocují na základě priority. Nejdříve se
vyhodnocují operátory s vyšší prioritou. Pokud má více operátorů stejnou prioritu, vyhodnocují se podle jejich asociativity.
Prioritu lze měnit pomocí kulatých závorek, ty mají nejvyšší prioritu.
• Binární operátory jsou asociativní zleva doprava. • Unární, přiřazovací, ternární a operátor nulového
sjednocení jsou asociativní zprava doleva.
Operátory a výrazy • Přehled operátorů http://msdn.microsoft.com/en-us/library/6a71f45d.aspx
Operátory • Poznámky: V C# se za operátor považují i () za účelem změny pořadí vyhodnocování.
V C++ jsou považovány za oddělovač.
V C# je operátor new operátorem volání konstruktoru. V C++ je alokátorem.
Praktický význam pro
programátora?
žádný!
V C# se alokace sturktur
neprovádí.
Operátory • Poznámky: V C# chybí oproti C++ některé operátory přetypování.
A také některé další operátory (např. pro třídní ukazatel)
static_cast, dynamic_cast,
reinterpret_cast a const_cast
.*, ->*,
typeid
Operátory • Poznámky: V C# lze některé operátory využít pouze v nezabezpečeném kódu.
stackalloc (alokace na zásobníku) sizeof (velikost hodnotového typu) -> (přístup ke složce struktury) * (dereferencování ukazatele) & (získání adresy)
Číselná rozšíření • Unární rozšíření Implicitní konverze uplatňující se pro předdefinované unární operátory +, - a ~
sbyte, byte, short, ushort a char se implicitně převádí na typ int uint se při použití minus převádí na long
Bitový doplněk
sbyte a = -1; a = -a; //Chyba, na rozdíl od C++ nelze implicitně konvertovat int na sbyte
Číselná rozšíření • Binární rozšíření Implicitní konverze obou operandů na společný typ (ten nebo bool bude
také typem výsledku) při použití předdefinovaných binárních operátorů +, -, *, /, %, &, |, ^, ==, !=, <, >, <=, >=.
Pravidla 1. Pokud je první z operandů decimal, převede se druhý též na decimal.
Chyba nastane, pokud je druhý operand typu float nebo double, protože neexistuje implicitní konverze.
V případě relačních operátorů.
Číselná rozšíření • Binární rozšíření Pravidla
2. Pokud je jeden operand double, druhý se převede na double. 3. Pokud je jeden operand typu float, druhý se převede na float. 4. Je-li jeden z operandů typu ulong, převede se druhý na ulong, pokud není
sbyte, short, int nebo long (jinak chyba). 5. Pokud je jeden z operandů typu long, převede se druhý na long.
Číselná rozšíření • Binární rozšíření Pravidla
6. Pokud je jeden z operandu uint a druhý je typu sbyte, short nebo int, převedou se oba operandy na long.
7. Jinak, pokud je jeden z operandů typu uint, převede se druhý na uint. 8. Jinak se oba operandy převedou na int.
Některé operátory • Operátor tečka Specifikuje přístup ke statickým a nestatickým složkám tříd, struktur,
výčtových typů a jmenných prostorů. Nahrazuje ., -> a :: z C++
• Typeof Zjišťování typu Podobný význam jako typeid z C++ Typeof lze použít jen na jméno typu, nikoli na výraz. K tomu lze použít
metodu GetType().
if ((a + b).GetType() == typeof(int)) { /* ... */ }
Některé operátory • is Zjišťování typu Syntaxe
výraz is typ výraz – výraz referenčního typu typ – jméno typu třídy, struktury či rozhraní
Vrací true pokud lze výraz přetypovat na typ. Pro hodnotové typy lze operátor is použít, pokud je výraz typu object.
Některé operátory • Přetypování Na rozdíl od C++ se vždy kontroluje, zda má přetypování smysl. (typ)
Stejně jako v C++. Pokud je při kompilaci zjištěna nemožnost přetypování, ohlásí překladač chybu.
Pokud se tato nemožnost objeví až za běhu programu, dojde k vyvolání výjimky System.InvalidCastException
as K přetypování mezi referenčními typy. Syntaxe
výraz as typ
Některé operátory • Přetypování as
Syntaxe výraz as typ
výraz – reference na instanci, která se má přetypovat nebo hodnota, která se má zabalit.
typ – cílový referenční typ Je-li možné přetypování je výsledkem typ, jinak null.
static void Vypis(object r) { string s = r as string; if (s != null) Console.WriteLine(s); }
Některé operátory • Bitové posuny << a >>
Levý operand musí být celočíselného typu. Sbyte, byte, short, ushort a char se převádí na typ int.
Pravý operand musí být typu int nebo se na něj musí nechat implicitně zkonvertovat.
Výsledkem je hodnota typu levého operandu.
Některé operátory • Rovná se, nerovná se == a !=
Lze použít i k porovnání zda reference ukazují na stejný objekt. Např. u string přetíženo na porovnání obsahu. Pokud v době překladu je známo, že reference ukazují na různé instance,
překladač ohlásí chybu. Lze porovnávat instance delegátů. Jsou si rovny, jsou-li stejného typu a:
oba obsahují null, nebo ukazují na tutéž statickou či nestatickou metodu a stejný objekt
Mohou ukazovat na více stejných metod.
Některé operátory • Podmíněný operátor ?: Na rozdíl od C++ smí být první operand pouze bool, nebo typ, který lze
implicitně přetypovat na bool, nebo obsahuje operator true. Typ výsledku výrazu A ? X : Y je určen podle: Je-li typ X a typ Y stejný, bude tento i typem výsledku.
Existuje-li implicitní konverze z typu výrazu X na typ výrazu Y a neexistuje-
li implicitní konverze z Y na X, bude typem výsledku typ Y.
Existuje-li implicitní konverze z typu výrazu Y na typ výrazu X a neexistuje-li implicitní konverze z typu X na typ Y, bude typem výsledku typ X.
Jinak nelze typ výsledku určit a překladač oznámí chybu.
Preprocesor • V C# se hovoří o tzv. „direktivách preprocesoru“ – nezpracovává je
však preprocesor jako v C++, ale překladač.
• Podmíněné symboly Neexistují makra Symbol #define
Slouží k definování podmíněného symbolu Syntaxe
#define identifikátor
Preprocesor • Podmíněné symboly Symbol #define
Musí být v programu před všemi ostatními direktivami preprocesoru (tedy i před using).
Identifikátor je definován od #define do konce zdrojového souboru. Definice podmíněného symbolu pro všechny překládané části sestavení: menu
Project > Properties, část Build, pole Conditional compilation symbols.
#define LADĚNÍ using System; // další příkazy programu
Preprocesor • Podmíněné symboly Symbol #undef
Slouží ke zrušení podmíněného symbolu zavedeného direktivou #define nebo ve vlastnostech profilu
Musí být v programu před všemi ostatními direktivami, které nejsou direktivami preprocesoru
Preprocesor • Podmíněný překlad Pomocí symbolu #if, podobně jako v C++ Schéma #if podmíněný_výraz_1 zdrojový_text_1 #elif podmíněný_výraz_2 zdrojový_text_2 // ... #else zdrojový_text_n #endif
#elif se může opakovat
několikrát
#elif a #else lze vynechat
Preprocesor • Podmíněný překlad Nejjednodušší použití
#define LADĚNÍ // ... #if LADĚNÍ Console.WriteLine("x = " + x); #endif
Preprocesor • Podmíněný překlad Složitější podmíněné výrazy – mohou obsahovat ==, !=, &&, ||, konstanty true a false.
Na rozdíl od C++ nemohou podmíněné výrazy obsahovat číselné konstanty ani konstanty definované pomocí const.
#if LADĚNÍ == false Console.WriteLine("x = " + x); #endif #if (LADĚNÍ || TESTOVÁNÍ) Console.WriteLine("y = " + y); #endif
Preprocesor • Podmíněný překlad Pokud je program v prostředí Visual Studio překládán v ladícím režimu (v
poli Solution Configuration je zvolena položka Debug), je automaticky definován symbol DEBUG.
Preprocesor • Chybová hlášení Direktiva #error
Podobně jako v C++. Způsobí výpis chybové zprávy a zastaví překlad.
Direktiva #warning
Způsobí výpis chybového hlášení, avšak nezastaví překlad.
Chybové hlášení - zpráva bez uvozovek. Případné uvozovky
jsou součástí výstupu.
Obě direktivy lze použít pouze ve zdrojovém textu
direktivy #if.
Preprocesor • Oblast zdrojového kódu Direktivy #region a #endregion
Slouží k vymezení oblasti. V C++ neexistují Syntaxe
#region zprávanep #endregion zprávanep
Visual Studio umožňuje takto označené oblasti „skrýt“ (zabalit)
Nesmí se křížit, mohou se vnořovat
Nemění význam programu. Jen
označují oblast ve zdrojovém kódu.
Preprocesor • Řádkování Direktiva #line
Umožňuje při překladu změnit počítání řádků a název souboru. Stejné jako v C++ Syntaxe
#line číslo název_souborunep
číslo – celé číslo, které má překladač považovat za aktuální číslo řádku. název_souboru – jaké jméno souboru má překladač považovat za aktuální (bez
uvozovek)
Preprocesor • Pragma Direktiva #pragma
Ke specifikaci kontextových informací pro překladač Význam může být v různých překladačích odlišný Ve Visual Studio 2012:
#pragma warning – zapnutí či vypnutí určitého varování #pragma checksum – generování kontrolního součtu zdrojového souboru
/// <summary> /// Základní barvy. /// </summary> public enum Barva { #pragma warning disable 1591 červená, zelená, modrá #pragma warning restore 1591 }
Číselný seznam varování,
oddělených čárkou
TŘÍDY • Rozdíly mezi C++ a C# V C# není podporována vícenásobná (a tedy i virtuální) dědičnost V C# lze dědičnost u tříd zakázat Všechny třídy v C# mají společného předka – object V C# lze vytvářet pouze dynamické instance, o jejich dealokaci se stará
automatická správa paměti. Význam destruktoru je podstatně menší než v C++. Třídy mohou implementovat rozhraní Mohou obsahovat kromě datových složek, metod, konstruktorů a
destruktorů také vlastnosti, události, přetížené operátory a vnořené typy.
TŘÍDY • Základní informace Deklarace modifikátorynep partialnep class identifikátor předeknep tělo;nep
modifikátory - přístupová práva, uvádí se pro každou složku zvlášť, mohou dále obsahovat specifikaci abstract, sealed, new či static
identifikátor – jméno deklarované třídy předek – předek třídy a implementovaná rozhraní tělo - skupina deklarací ve složených závorkách; může však být i
prázdné modifikátor partial – definice částečné třídy Deklarace třídy nemůže být jako lokální typ (v metodě), ale jen v
prostoru jmen či v jiné třídě/struktuře (jako vnořený typ)
TŘÍDY • Základní informace Přístupová práva
Určují přístupnost složek v rámci třídy, v potomcích, v sestavení této třídy a v ostatních sestaveních
stupeň třída sestavení potomci ostatní public X X X X protected internal X X X protected X X internal X X private X
TŘÍDY • Základní informace Přístupová práva
private (soukromé složky) a protected (chráněné složky) – stejný význam jako v C++
public (veřejné složky) – pokud má třída jako celek modifikátor public, je pak daná složka veřejně přístupná všem sestavením.
internal (interní, vnitřní složky) – složka je přístupná všem součástem sestavení, v němž je třída definována.
protected internal – sjednocení přístupového práva internal a protected. Složka je přístupná jak ve tříde, v níž je deklarována, tak ve všech potomcích bez ohledu na sestavení.
TŘÍDY • Základní informace Přístupová práva
Není-li specifikátor přístupových práv uveden, jedná se o složku soukromou (private).
Datový typ složky nesmí mít širší přístupová práva než kterýkoli z dalších typů uvedených v deklaraci. Př.: potomek nesmí mít širší přístupová práva než předek. Př.: metoda nesmí mít širší přístupová práva, než typ, který vrací.
class A {} // je internal public class B { public A f() { /* ... */ } // Chyba }
internal vs.
public
TŘÍDY • Datové složky Deklarace je podobná deklaraci proměnných. Lze navíc použít atributy a modifikátory new a readonly. Statické a nestatické.
TŘÍDY • Datové složky Nestatické datové složky
Instance fields Složky jednotlivých instancí (stejně jako v C++) Třída může obsahovat datovou složku svého vlastního typu:
public class Prvek { private Prvek další; // OK // ... }
odkaz (ukazatel) na instanci
třídy
TŘÍDY • Datové složky Nestatické datové složky
Deklarace může obsahovat inicializaci
public class A { private int x = 10; private string s = "Text"; // ... }
Takovéto přiřazení hodnot proběhne ještě
před voláním konstruktoru
Pokud nedojde k inicializaci, inicializuje překladač před voláním konstruktoru hodnoty
na 0, false nebo null.
TŘÍDY • Datové složky Statické datové složky
Jako v C++ jsou společné pro všechny instance třídy. Existují již od zavedení třídy do paměti (tedy bez nutnosti existence instance). Deklarují se pomocí static. Ty statické datové složky, které nejsou inicializovány před voláním konstruktoru
(existují statické konstruktory), překladač inicializuje hodnotou 0, false nebo null.
Při použití mimo třídu je nutné je kvalifikovat jménem třídy.
TŘÍDY • Datové složky Neměnné datové složky
Konstanty Musí se inicializovat přímo v deklaraci Deklarace pomocí const
Je automaticky statická, static nelze použít Lze takto deklarovat hodnotové datové typy (základní typy), výčtový typ a string. Pro
referenční typy (třídy, …) jej lze použít pouze k deklaraci proměnné inicializované null. Deklarace pomocí readonly – složku lze inicializovat až konstruktorem, pak už
ji nelze měnit.
TŘÍDY • Datové složky Neměnné datové složky
Příklad: https://goo.gl/WdetzM
class A { const int x = 10; readonly int y = 20; public A(int y) { this.y = y; // OK x = 20; // Chyba }
public void Set(int y) { this.y = y; // Chyba }
}
TŘÍDY • Datové složky Neměnné datové složky
Příklad: https://goo.gl/eeckXC
struct Struktura { public int x; public void SetX(int x) { this.x = x; } } class Trida { public readonly int[] pole = new int[] {1, 2}; public readonly Struktura b; } class Program { static void Main(string[] args) { Trida a = new Trida(); a.pole[0] = 5; // OK //a.b.x = 20; // Chyba a.b.SetX(20); // OK } }
TŘÍDY • Datové složky Doporučení
Nestatické datové složky by se neměly deklarovat jako veřejné nebo chráněné. Přístup k nim pak realizovat pomocí vlastností.
Pro předdefinovanou instanci třídy nebo struktury, která neobsahuje metody, jež umožňují modifikovat její datové složky (angl. immutable type), se má deklarovat veřejná statická datová složka s modifikátorem readonly.
TŘÍDY • Metody Odlišnosti od C++
Neexistuje obdoba inline metody. Vedle předávání parametrů hodnotou či odkazem existují v C# ještě tzv. výstupní
parametry. Metody v C# lze přetěžovat na základě způsobu předávání parametrů. Nelze předepsat implicitní hodnoty parametrů. Nelze využívat prototyp v těle třídy a deklaraci provést mimo. Ta musí být vždy v
těle třídy.
TŘÍDY • Metody Odlišnosti od C++
Syntaxe modifikátorynep typ identifikátor ( seznam_parametrůnep ) tělo modifikátory – přístupová práva, vyjadřují, zda jde o statickou, virtuální,
abstraktní, zapečetěnou, předefinovanou, zastiňující metodu nebo externí metodu z klasické dynamické knihovny. Lze uvést též unsafe (nezabezpečený kód metody).
typ – typ návratové hodnoty. Možno i void. identifikátor - jmenovka metody. Nemusí být unikátní, pokud se liší
signatura.
Počet, pořadí, typ a způsob předávání
parametrů.
TŘÍDY • Metody Odlišnosti od C++
Syntaxe modifikátorynep typ identifikátor ( seznam_parametrůnep ) tělo seznam_parametrů – seznam formálních parametrů metody. Možno vynechat. tělo - obvykle blok příkazů. U abstraktních a externích metod je tělo tvořeno
středníkem.
TŘÍDY • Metody Nestatické metody Nestatické metody (angl. instance methods) implementují
operace s jednotlivými instancemi. Volají se pro konkrétní instanci. Jméno nestatické metody volané mimo její třídu se kvalifikuje jménem instance její třídy. V deklaraci není uveden modifikátor static. V těle metody je k dispozici odkaz na aktuální instanci
vyjádřený klíčovým slovem this, stejně jako v C++. Odkazem this lze kvalifikovat pouze nestatické složky třídy.
TŘÍDY • Metody Nestatické metody Příklad
class TridaA { int a; TridaA(int a) {
this.a = a; }
}
TŘÍDY • Metody Statické metody
Statická metoda se deklaruje s modifikátorem static Implementují operace s třídou jako celkem, ne instancemi. Není k dispozici this a nelze pracovat s nestatickými složkami bez kvalifikace
instance.
TŘÍDY • Metody Statické metody
Příklad
class Program { void Run() // nestatická metoda { Console.WriteLine("Zavolána nestatická metoda Run"); } static void Main(string[] args) // statická metoda { Program program = new Program(); program.Run(); } }
TŘÍDY • Metody Předávání parametrů
V C# lze předávat parametry metody hodnotou nebo odkazem a používat výstupní parametry. Lze také deklarovat metodu s proměnným počtem parametrů - obdoba výpustky v C++.
Syntaxe typ identifikátor
Typ – typ parametru. Identifikátor – jméno parametru.
TŘÍDY • Metody Předávání parametrů hodnotou
Význam parametru předávaného hodnotou je stejný jako v C++ - vytváří se lokální proměnná v těle metody, do které se uloží hodnota skutečného parametru.
V případě hodnotových parametrů se skutečný parametr nemění. U referenčních typů se předává odkaz na instanci -> může dojít ke změně datové složky
odpovídající instance. Skutečným parametrem může být jakýkoli výraz, který lze implicitní konverzí převést na
typ parametru.
TŘÍDY • Metody Předávání parametrů hodnotou
Příklad: https://goo.gl/tk5ReL
class TridaA { public int x, y; public TridaA(int x, int y) { this.x = x; this.y = y; } } struct StrukturaB { public int x, y; public StrukturaB(int x, int y) { this.x = x; this.y = y; } } class Program { static void f(TridaA a1, TridaA a2, StrukturaB b) { a1.x = b.x; // složka x se ve skutečném parametru změní a2 = a1; // skutečný parametr se nezmění b.y = a1.y; // skutečný parametr se nezmění } static void Main(string[] args) { TridaA a1 = new TridaA(10, 20), a2 = new TridaA(30, 40); StrukturaB b = new StrukturaB(50, 60); f(a1, a2, b); // změna pouze u složky a1.x = 50 } }
TŘÍDY • Metody Předávání parametrů odkazem
Význam předávání parametru odkazem je stejný jako v C++, syntaxe je ovšem jiná – před typ se zapisuje modifikátor ref.
Syntaxe ref typ identifikátor
Formální parametr v metodě představuje vlastně jen jiné jméno pro skutečný parametr, takže veškeré operace prováděné s formálním parametrem se ihned projeví v hodnotě skutečného parametru. Při volání metody se před skutečný parametr musí zapsat
modifikátor ref. Skutečným parametrem musí být proměnná stejného typu,
jaký je uveden v deklaraci formálního parametru nebo typu, který je potomkem typu formálního parametru.
TŘÍDY • Metody Předávání parametrů odkazem Příklad
class Program { static void g(ref TridaA a1, ref TridaA a2, ref StrukturaB b) { a1.x = b.x; a2 = a1; // a2 se bude odkazovat na a1 b.y = a1.y; } static void Main(string[] args) { TridaA a1 = new TridaA(10, 20), a2 = new TridaA(30, 40); StrukturaB b = new StrukturaB(50, 60); g(ref a1, ref a2, ref b); // všechny instance mají složky x = 50, y = 20 Console.ReadLine(); } }
TŘÍDY • Metody Výstupní parametry Výstupní parametry se liší od parametrů předávaných
hodnotou tím, že jako skutečný výstupní parametr lze předat proměnnou, která nebyla inicializována. V metodě se musí výstupní parametr inicializovat, i když byl skutečný parametr před voláním této metody inicializován. Před formálním i skutečným parametrem se musí zapsat
modifikátor out. Syntaxe
out typ identifikátor
TŘÍDY • Metody Výstupní parametry Příklad
class Program { static void f(int x, out int y) { // Console.WriteLine("y = " + y); // Chyba - y není inicializ. y = x; Console.WriteLine("y = " + y); // OK } static void Main(string[] args) { int x = 20, y; f(x, out y); // proměnná x musí být inicializována } }
TŘÍDY • Metody Neznámý počet parametrů
Jestliže předem není známý počet a typ parametrů metody, lze v C# použít modifikátor params. Tento modifikátor lze použít u posledního z formálních parametrů metody. V normě C# se tento parametr nazývá parameter array.
Syntaxe params typ_pole identifikátor
Typ pole – typ jednorozměrného pole. Nelze v něm použít modifikátory ref a out.
Skutečným parametrem může být: Pole odpovídajícího typu – parametr s modifikátorem params se chová
stejně jako parametr předávaný hodnotou. Seznam výrazů oddělených čárkou, jejichž hodnoty lze implicitními
konverzemi převést na typ prvků pole formálního parametru.
TŘÍDY • Metody Neznámý počet parametrů
class Matematika { public static int Maximum(params int[] data) { if (data.Length == 0) return 0; int max = data[0]; foreach (int a in data) { if (a > max) max = a; } return max; } } class Program { static void Main(string[] args) { Console.WriteLine("Maximum = {0}", Matematika.Maximum(5, 88, -3)); int[] pole = { 9, 8, 7 }; int max = Matematika.Maximum(pole); Console.WriteLine("Maximum = {0}", max); } }
• Metody Přetěžování V C# lze deklarovat v jedné třídě metody se stejným
identifikátorem, pokud se liší v počtu, typu, pořadí nebo způsobu předávání parametrů.
Lze deklarovat void f() {}
void f(int x) {}
void f(char x) {}
void f(ref char x) {}
void f(char x) {}
void f(out char x) {}
TŘÍDY
• Metody Přetěžování Nelze deklarovat metody lišící se jen ref a out.
void f(ref char x) {}
void f(out char x) {} //chyba
Nelze rozlišit metody pouze podle typu návratové hodnoty nebo podle modifikátoru params.
Pokud skutečné parametry neodpovídají, používají se pravidla pro nalezení „nejlepší shody“. stejný počet parametrů + nejsnažší implicitní konverze typů skutečných na
formální typy metody.
TŘÍDY
• Metody - doporučení Výstupní parametry vždy uvádět až na konci seznamu parametrů metody
(kromě parametrů s modifikátorem params)
Přetížené metody by měly mít parametry stejného typu a názvu uvedené ve stejném pořadí (kromě těch s modifikátory out a params, které by měly být na konci).
TŘÍDY
• Metody - doporučení Pokud mají být přetížené metody virtuální, má být deklarována jako
virtuální pouze jedna verze přetížené metody, která má největší počet parametrů. Ostatní verze metody, by měly volat tuto verzi.
TŘÍDY
public void Write(string message, FileStream stream) { this.Write(message, stream, false); } public virtual void Write(string message, FileStream stream, bool closeStream) { // vlastní činnost }
• Metody – doporučení Pokud některý parametr metody je volitelný, měly by se vytvořit dvě
přetížené verze této metody, jedna s tímto parametrem a druhá bez tohoto parametru.
Parametr referenčního typu, pro který je povolena hodnota null by se neměl chápat jako volitelný parametr, ale jako parametr, pro nějž se má použít nějaká implicitní hodnota.
TŘÍDY
• Externí metody V C# lze volat i funkce napsané v C/C++ nebo v jiném programovacím
jazyce a uložené v klasické dynamické knihovně pro neřízený kód. Tato funkce musí být deklarována v C# jako statická externí metoda s atributem DllImport, ve kterém se uvede jméno souboru dynamické knihovny.
Takto lze volat i funkce z Windows API. Syntaxe
[DllImport( knihovna )] modifikátorynep static extern typ identifikátor (seznam_parametrůnep);
TŘÍDY
• Externí metody Příklad
TŘÍDY
class Program { [DllImport("user32.dll")] static extern int MessageBox(int windowHandle, string text, string caption, uint type); static void Main(string[] args) { MessageBox(0, "Ukázka volání funkce MessageBox z Windows API", "Hlášení", 0); } }
• Metoda Main Vstupní bod konzolové i okenní aplikace. Má 4 podoby: static int Main() {} static int Main(string[] args) {} static void Main() {} static void Main(string[] args) {}
Libovolný specifikátor přístupových práv. Pokud je návratový typ int, vrací kód ukončení programu – jako v C++
TŘÍDY
• Metoda Main Parametr args představuje parametry předávané z příkazové řádky.
Na rozdíl od C++ není nultým parametrem název programu. Lze ji přetěžovat, ale jako vstupní bod může sloužit jen jedna z výše
uvedených. Může se nacházet ve více třídách – nutno ale určit, která se použije jako
vstupní bod programu. pomocí přepínače: /main:třída pomocí menu: Project | Properties, část Application, rozevírací seznam Startup
object
TŘÍDY
• Podmíněné metody Metodu s návratovým typem void lze deklarovat jako podmíněnou s
atributem Conditional. Syntaxe
[ Conditional ( "jméno" ) ] deklarace_metody_typu_void Jméno – identifikátor podmíněného symbolu deklarovaného pomocí direktivy #define. Je-li identifikátor jméno definován, přeloží se tato metoda i všechna její volání stejně, jako
kdyby atribut Conditional nebyl u metody uveden. Pokud identifikátor jméno není definován, překladač odstraní všechna volání této metody
ze zdrojových textů programu, ale metodu přeloží.
TŘÍDY
• Podmíněné metody Příklad
TŘÍDY
#define LADĚNÍ using System; using System.Diagnostics; class Ladění { [Conditional("LADĚNÍ")] public static void Výpis(string s, int x) { Console.WriteLine("Proměnná {0} má hodnotu {1}", s, x); } } class Program { static void Main(string[] args) { int a = 1, b = 2; Ladění.Výpis("a", a); // #1 Ladění.Výpis("b", b); // #2 Console.WriteLine("Součet proměnných je {0}", a + b); Console.ReadKey(); } }
• Konstruktory Podobně jako v C++ slouží k vytvoření a inicializaci instance. Rozdíly oproti C++
V C# lze deklarovat statické konstruktory V C# může konstruktor volat jiný konstruktor téže třídy Konstruktor v C# nelze použít k implicitní konverzi V C# neexistuje kopírovací konstruktor V C# je konstruktor volán vždy explicitně pomocí new
TŘÍDY
• Konstruktory instanční konstruktor
známý z C++ Syntaxe
modifikátorynep identifikátor ( seznam_parametrůnep ) inicializátornep tělo Inicializátor
:this (seznam_parametrůnep) :base (seznam_parametrůnep)
Modifikátory – přístupová práva Identifikátor – shodný s názvem třídy, které je konstruktorem
TŘÍDY
Volání jiného konstruktoru téže třídy
Volání konstruktoru bezprostředního předka třídy
• Konstruktory instanční konstruktor
nedědí se Pokud neexistuje uživatelem definovaný konstruktor, vytváří překladač
implicitní, veřejně přístupný konstruktor bez parametrů a s prázdným tělem.
TŘÍDY
• Konstruktory instanční konstruktor
Příklad: https://goo.gl/bk5T9P
TŘÍDY
class Matice { double[,] a; public Matice(int m, int n) { a = new double[m, n]; } public Matice(int m, int n, double hodnota) : this(m, n) { for (int i = 0; i < a.GetLength(0); i++) { for (int j = 0; j < a.GetLength(1); j++ ) a[i, j] = hodnota; } } public void Výpis() { ... } } class Program { static void Main(string[] args) { Matice matice = new Matice(3, 2); matice.Výpis(); matice = new Matice(3, 2, 10.54); matice.Výpis(); // Matice matice2 = new Matice(); // Chyba - třída nemá implicitní konstruktor } }
• Konstruktory instanční konstruktor
doporučení parametr konstruktoru vlastnosti (a jí případně odpovídající datové složky) by měl mít
stejný název jako vlastnost – rozdíl je pouze ve velikosti písmen. Parametr velbloudím a veřejná/chráněná vlastnost pascalovským stylem.
TŘÍDY
• Konstruktory statický konstruktor
Neexistuje v C++ Slouží k inicializaci statických složek Syntaxe
static identifikátor ( ) tělo identifikátor – shodný s názvem třídy V těle (bloku příkazů) lze pracovat jen se statickými složkami třídy
Nedědí se Nelze jej volat explicitně (volá jej program při zavedení třídy do paměti)
TŘÍDY
• Konstruktory statický konstruktor
Nemá modifikátor přístupových práv Neobsahuje parametry
TŘÍDY
• Konstruktory statický konstruktor
Příklad
TŘÍDY
class Matice { double[,] a; static readonly int rozměr; static Matice() { Console.Write("Zadej rozměr matice: "); rozměr = Convert.ToInt32(Console.ReadLine()); } public Matice() { // rozměr = 3; // Chyba a = new double[rozměr, rozměr]; } public Matice(double hodnota) : this() { for (int i = 0; i < rozměr; i++) { for (int j = 0; j < rozměr; j++) a[i, j] = hodnota; } } // ... }
• Destruktor V nové normě C# je nazýván finalizér (finalizer). Jiná úloha než v C++ V C# jde o zkratku metody finalize třídy object. Tu volá automatická
správa paměti při uvolnění instance z paměti. Destruktor lze využít tedy např. k uvolnění jiných prostředků než je
operační paměť (otevřené soubory, síťová připojení) Syntaxe
~ identifikátor ( ) tělo identifikátor třídy, v němž je konstruktor deklarován
TŘÍDY
• Destruktor Nemá přístupová práva Nemá parametry Nedědí se Nelze jej volat explicitně V C# se používají zřídka
V rozsáhlejších programech mohou způsobovat zdržení při automatickém úklidu paměti. Objekty s destruktory nejsou odstraněny přímo, ale odkládají se stranou, pak se volá jejich finalize metoda, a pak se teprve odstraňují.
Destruktory se zpravidla kombinují s metodou Dispose() rozhraní IDisposable
TŘÍDY
• Vlastnosti (properties) V normě C++ neexistují. Lze se s nimi setkat v rámci některých rozšířeních
jazyka C++ (Borland C++ Builder). Syntaktická zkratka pro dvojici přístupových metod. Používá se prostřednictvím svého identifikátoru. Přiřazením hodnoty do
identifikátoru se volá jedna z přístupových metod. Ta může např. uložit danou hodnotu do soukromé datové složky či provést i řadu jiných operací.
Při použití hodnoty vlastnosti se volá druhá z metod. Ta může vrátit hodnotu uloženou v soukromé datové složce či hodnotu získanou např. výpočtem.
TŘÍDY
• Vlastnosti (properties) Syntaxe modifikátorynep typ jméno { část_set část_getnep } modifikátorynep typ jméno { část_get část_setnep }
Modifikátory - modifikátory přístupových práv a modifikátory vyjadřující, zda jde o statickou, virtuální, abstraktní, zapečetěnou, předefinovanou nebo zastiňující vlastnost.
Typ – typ hodnoty vlastnosti Jméno – identifikátor vlastnosti Část_set – přístupová metoda pro nastavení hodnoty vlastnosti Část_get – přístupová metoda pro zjištění hodnoty
TŘÍDY
• Vlastnosti (properties) Na pořadí uvedení části get a části set nezáleží.
Jednu z nich lze vynechat. Vynecháním části set se získá vlastnost „jen pro čtení“. Vynecháním části set se získá vlastnost „jen pro zápis“. Syntaxe částí get a set
modifikátorynep get tělo modifikátorynep set tělo
Tělo – blok příkazů, nebo jen středník u abstraktní vlastnosti. Modifikátory - Jsou-li modifikátory u dané části uvedeny, musí reprezentovat
přístupové právo, které více omezuje přístup k dané části než modifikátory přístupových práv pro vlastnost jako celek.
TŘÍDY
• Vlastnosti (properties) Modifikátory přístupových práv
TŘÍDY
Ve vlastnosti Povolené mod. u get a set public protected internal
internal protected private
protected internal internal protected private
internal private
protected private
private
• Vlastnosti (properties) V těle části set existuje proměnná value typu shodného
s typem vlastnosti. value obsahuje hodnotu přiřazovanou vlastnosti
Tělo části get musí obsahovat příkaz return výraz; ten poskytuje vrácenou hodnotu vlastnosti
TŘÍDY
• Vlastnosti (properties) Příklad: https://goo.gl/i8pzgB
TŘÍDY
class KomplexCislo { private int real; private int imag; public KomplexCislo(int real, int imag) { this.real = real; this.imag = imag; } public int Real { get { return real; } set { real = value; } } public int Imag { get { return imag; } set { imag = value; } } public double Abs { get { return Math.Sqrt(Real*Real + Imag*Imag); } } } class Program { static void Main(string[] args) { KomplexCislo kc = new KomplexCislo(10, 20); Console.WriteLine("Absolutní hodnota čísla ({0} + {1}i) je {2:F1}", kc.Real, kc.Imag, kc.Abs); kc.Real = 3; kc.Imag = 4; Console.WriteLine("Absolutní hodnota čísla ({0} + {1}i) je {2:F1}", kc.Real, kc.Imag, kc.Abs); Console.ReadKey(); } }
• Vlastnosti (properties) Statické vlastnosti Statická vlastnost je deklarována s modifikátorem static. Lze ji použít, i v případě, že neexistuje žádná instance dané
třídy. Při použití statické vlastnosti mimo její třídu se kvalifikuje
jménem její třídy. Část get a část set statické vlastnosti může pracovat pouze
se statickými složkami dané třídy.
TŘÍDY
• Vlastnosti (properties) Doporučení Vlastnost by měla mít stejný název jako její odpovídající datová
složka. Rozdíl je pouze ve velikosti písmen. Pro soukromou datovou
složku se používá velbloudí styl a pro veřejnou a chráněnou vlastnost pascalovský styl.
Pokud se jedná o řádově pomalejší operaci, než je nastavení hodnoty do datové složky třídy (např. přístup k souboru), doporučuje se použít místo vlastnosti metodu.
Nedoporučuje se deklarovat vlastnosti, které umožňují pouze nastavení hodnoty (to lze provést metodou).
Nastavení vlastností dané třídy by nemělo být závislé na pořadí. Pokud při nastavení vlastnosti dojde k vyvolání výjimky, měla by
si vlastnost uchovat předchozí hodnotu. Část get by neměla vyvolávat výjimku.
TŘÍDY
• Události Jazyk C# oproti C++ nabízí jednotný mechanismus zpracování událostí.
Založeno na návrhovém vzoru vydavatel – předplatitel (publisher - subscriber). Instance, v níž může událost vzniknout (vydavatel), si vede
seznam instancí, jež mají zájem na tuto událost reagovat (předplatitelů).
Instance, jež má zájem reagovat na určitou událost, tedy předplatitel, si u vydavatele registruje metodu, která se o reakci postará. K tomu použije delegát.
Nastane-li událost, vyrozumí o ní vydavatel své předplatitele tím, že zavolá jejich registrované metody.
TŘÍDY
• Události Handler a údaje o události Metody, které reagují na události, se označují jako handlery.
Jsou vždy typu void. Mají dva parametry (object – odkaz na instanci jež vyvolala událost a System.EventArgs).
Signatura void Stisk(object sender, EventArgs e) {}
Knihovna BCL obsahuje řadu potomků třídy EventArgs pro běžné typy událostí, jako je stisknutí tlačítka myši, stisknutí klávesy apod.
Pokud událost umožňuje stornování činnosti, druhý parametr handleru by měl být typu CancelEventArgs (nebo jeho potomek). Typ CancelEventArgs obsahuje jednu vlastnost Cancel, která poskytuje nebo nastavuje hodnotu, udávající, zda činnost, která je s událostí spojená, by měla být stornována.
TŘÍDY
• Události Handler a údaje o události Pro zpracování událostí nabízí knihovna BCL celou řadu
předdefinovaných delegátů. Lze si však deklarovat i vlastní. Identifikátor delegátu by měl obsahovat příponu EventHandler. Potomek třídy EventArgs by měl obsahovat příponu EventArgs.
Delegát může obsahovat ukazatele na více handlerů.
TŘÍDY
delegate void NecoSeStaloEventHandler(object sender, MojeEventArgs e);
• Události Deklarace události
Jako složka třídy Syntaxe modifikátorynep event typ jméno ; modifikátorynep event typ jméno { části_add_remove }
Modifikátory – modifikátory přístupových práv a modifikátory vyjadřující, zda jde o statickou, virtuální, abstraktní, zapečetěnou, předefinovanou nebo zastiňující událost. Typ – identifikátor delegátu. Jméno – jméno (identifikátor) události. Zpravidla odpovídá
identifikátoru typ bez přípony EventHandler.
TŘÍDY
• Události Deklarace události Klíčové slovo event zamezí vyvolání události mimo třídu, v níž je
událost deklarována. Mimo třídu události lze používat pouze operátory += a -= pro
registraci/zrušení registrace handleru. Událost je tedy vícenásobný delegát, který nelze zavolat mimo jeho
třídu. Událost by měla vyvolávat chráněná virtuální metoda třídy, v níž je
událost deklarována. Název této metody by měl být ve tvaru OnJmeno, kde Jmeno je jméno
události. Ta má jeden parametr odpovídající druhému parametru události (typu EventArgs, případně jeho potomka).
První parametr vyvolané události by neměl mít hodnotu null, kromě statické události. Druhý parametr by neměl být nikdy null. Třída EventArgs nabízí statickou datovou složku jen pro čtení Empty, která by se měla použít místo vytvoření nové instance této třídy.
TŘÍDY
• Události – Příklad: https://goo.gl/D5YlSk
TŘÍDY public delegate void ZmenaSouradnicEventHandler(object sender, EventArgs e); class Bod { private int x; private int y; public event ZmenaSouradnicEventHandler ZmenaSouradnic; public int X { get { return x; } set { if (x != value) { x = value; OnZmenaSouradnic(EventArgs.Empty); } } } public int Y { get { return y; } set { if (y != value) { y = value; OnZmenaSouradnic(EventArgs.Empty); } } } public Bod(int x, int y) { this.x = x; this.y = y; } protected virtual void OnZmenaSouradnic(EventArgs e) { ZmenaSouradnicEventHandler handler = ZmenaSouradnic; if (handler != null) handler(this, e); } } class Obrazek { public static void ZmenaSouradnic(object sender, EventArgs e) { Console.WriteLine("Došlo ke změně souřadnic bodu"); Console.WriteLine("Nové souřadnice bodu jsou: x = {0}, y = {1}", (sender as Bod).X, (sender as Bod).Y); } }
class Program { static void Main(string[] args) { Bod bod = new Bod(10, 20); bod.ZmenaSouradnic += new ZmenaSouradnicEventHandler(Obrazek.ZmenaSouradnic); // dtto od verze .NET 2.0: // bod.ZmenaSouradnic += Obrazek.ZmenaSouradnic; bod.X = 30; // Chyba - nelze volat událost mimo její třídu: // bod.ZmenaSouradnic(typeof(Program), new EventArgs()); Console.ReadKey(); } }
• Události - Příklad V metodě OnZmenaSouradnic je vytvořena kopie události, aby nevznikla
kolize při přístupu k události z více podprocesů. Pokud by se nevytvořila kopie, tak by po vyhodnocení výrazu v příkazu if, který testuje zda událost není null, mohl jiný podproces zrušit registraci handleru této události a pokud se jednalo o jediného předplatitele události, událost by měla hodnotu null a při jejím vyvolání by vznikla výjimka typu System.NullReferenceException. Lokální kopie události se sice odkazuje na stejnou instanci vícenásobného delegátu, ale pokud se k události registruje další handler, lokální kopie se již bude odkazovat na jinou instanci.
TŘÍDY
• Události jako vlastnosti Obsahuje-li třída větší množství událostí a jejich handlerů, může být
rozumné deklarovat je jako vlastnosti. Umožňuje použít vlastní způsob ukládání handlerů, např. do hešovací
tabulky nebo jiné kolekce. Událost se deklaruje jako vlastnost také v případě, pokud je potřebné
ošetřovat volání každého zaregistrovaného handleru zvlášť. Syntaxe
add blok //přístupová metoda pro přidání handleru do kolekce handlerů remove blok //přístupová metoda pro odebrání handleru z kolekce handlerů
TŘÍDY
• Události jako vlastnosti – příklad: https://goo.gl/bNKlxD
TŘÍDY
class Bod { private int x; private int y; private ZmenaSouradnicEventHandler zmenaSouradnic; public event ZmenaSouradnicEventHandler ZmenaSouradnic { add { lock (this) zmenaSouradnic += value; } remove { lock (this) zmenaSouradnic -= value; } } protected virtual void OnZmenaSouradnic(EventArgs e) { ZmenaSouradnicEventHandler handler = zmenaSouradnic; if (handler != null) handler(this, e); } // zbytek stejný jako v předchozím příkladu