- Generika
- Výjimky
- Události
V tomto díle kurzu budeme naposledy využívat prostředí konzole. Abychom mohli vyrazit dále, potřebujeme ovládat následující.
Generika
.NET Framework 2.0 přivedl na svět způsob, jak se zbavit boxingu a unboxingu – generiku. Generika umožňuje pracovat s objekty, o kterých nevíme, jakého typu jsou.
V prvním díle tohoto kurzu jsem si popsali práci se seznamem pomocí třídy List:
List<float> seznam = new List<float>();
Jak vidíte, při vytváření instance zadáváme do špičatých závorek název třídy, se kterou bude seznam pracovat. Třída pak při manipulaci s objekty nemusí provádět boxing a unboxing. Tato třída je tedy typickým představitelem generiky.
Nyní si ukážeme příklad definice naší vlastní generické třídy:
class GenericClass<X, Y>
{
private X prvekX;
private Y prvekY;
public GenericClass(X valX, Y valY)
{
prvekX = valX;
prvekY = valY;
}
public override string ToString()
{
return "Prvek X: "+prvekX.ToString()+"\nPrvek Y: "+prvekY.ToString();
}
public string GetInfo()
{
return "Prvek X implementuje "+prvekX.GetType().GetMethods().Length.ToString()+" metod\nPrvek Y implementuje "+prvekY.GetType().GetMethods().Length.ToString()+" metod";
}
}
Použijeme naši třídu:
GenericClass<DateTime, string> test = new GenericClass<DateTime, string>(DateTime.Now,"nějaký text");
Console.WriteLine(test.ToString());
Console.WriteLine(test.GetInfo());
Nadefinovali jsme si generickou třídu, která pracuje s prvky typu X a Y. V konstruktoru těmto prvkům přiřadíme hodnoty. Metoda GetInfo vrací řetězec, který obsahuje počet metod třídy X a Y. Metoda ToString jednoduše zavolá metody ToString třídy X a Y.
Dále jsem si vytvořili instanci této třídy a nadefinovali jsme si, že třída X bude typu DateTime a třída Y bude typu string.
Generické mohou být samozřejmě i metody a vlastnosti. Zde je metoda, která rozhodne zda-li jsou dva parametry stejného typu:
public bool Porovnej<T>(T val1, T val2)
{
return val1.Equals(val2);
}
Generika přinesla významný přírůst výkonu hlavně při používání generických seznamů či slovníků. Proto doporučuji vždy pracovat s generickou variantou (pokud existuje).
Výjimky
Výjimky poskytují v .NET Frameworku způsob, jak reagovat na nečekané události. Uživatel např. zadá místo čísla text nebo soubor, který chceme otevřít, je využíván jiným procesem. To vše a mnoho víc zachycují výjimky.
Výjimky můžeme zachytávat blokem try-catch nebo try-catch-finally. Do bloku try vložíme „podezřelý“ kód – kód, ve kterém by mohlo dojít k nečekané události. Ihned za blokem try následuje blok catch, ve kterém napíšeme kód reagující na výjimečný stav vyvolaný v bloku try. Za blokem try může následovat blok finally, který se vykoná v každém případě.
Výjimky tvoří objekty třídy Exception nebo jeho potomci. V bloku catch dostaneme instanci třídy Exception nebo jeho potomka jako parametr. Třída obsahuje podrobné informace o výjimce.
Nyní si uvedeme příklad:
static void Main(string[] args)
{
try
{
//Tento blok je pod kontrolou
int cislo1 = Int32.Parse(Console.ReadLine());
int cislo2 = Int32.Parse(Console.ReadLine());
int vysledek = cislo1 / cislo2;
Console.WriteLine(vysledek.ToString());
}
catch (DivideByZeroException e1)
{
//Tento blok se vykoná, když dojde k dělení nulou
Console.WriteLine("Pokus o dělení nulou.");
}
catch (Exception e2)
{
//Tento blok se vykoná při jakékoliv jiné chybě
Console.WriteLine("Jiná chyba: " + e2.Message);
}
finally
{
//Text se vypíše vždy
Console.WriteLine("Tento text se vypíše vždy.");
}
Console.Read();
}
Tento kód přijme od uživatele dvě čísla, vydělí je a vypíše výsledek. Celý hlavní kód je v bloku try. Za ním následuje první blok catch s parametrem typu DivideByZeroException. Znamená to tedy, že první blok catch bude vykonán, jen když se vyvolá výjimka typu DivideByZeroException (dělení nulou). Druhý blok catch má parametr typu Exception, což je předek všech dalších výjimek (včetně DivideByZeroException). Tento se tedy vykoná při každém vyvolání výjimky, kromě dělení nulou, protože ten zachytí již první blok.
Můžete nadefinovat libovolné množství bloku catch. Pamatujte však, že nejprve nadefinujete „specializované“ bloky, protože nikdy se nevolá více než jeden blok catch.
Řekli jsme si, že všechny výjimky jsou odvozeny od třídy Exception. Můžeme si tedy nadefinovat vlastní výjimku:
class MyException : Exception
{
private DateTime time;
public MyException(string message)
: base(message)
{
time = DateTime.Now;
}
public DateTime Time
{
get { return time; }
}
}
Třída naší výjimky si navíc během volání konstruktoru uloží aktuální čas.
Výjimky můžeme vyvolávat sami. Vyvoláme tedy naši výjimku:
throw new MyException("Zpráva o chybě");
Nejdříve uvedeme klíčové slovo throw a pak založíme instanci příslušné výjimky.
Události
Události jsou základem tvorby uživatelských aplikací. Pomocí událostí komunikujeme s uživatelem, operačním systémem nebo s jinou částí aplikace. Pro nás události reprezentují způsob, jak komunikuje objekt s objektem.
Jako příklad si vytvoříme třídu s jedinou členskou proměnnou. Když ji změníme, vyvolá se událost s údaji, kdy k události došlo, a nová hodnota proměnné.
Pro obsluhu události musíme definovat delegáta, což je ukazatel na metodu. Pro událost potřebujeme dva parametry, a to objekt, který událost vyvolává, a data události. Pro jejich přenos si vytvoříme tuto třídu:
class MyEventArgs : EventArgs
{
private DateTime time;
private string newValue;
public MyEventArgs(DateTime time, string newValue)
{
this.time = time;
this.newValue = newValue;
}
public DateTime Time
{
get { return time; }
}
public string NewValue
{
get { return newValue; }
}
}
Protože objekt vyvolávající událost může být cokoliv, používáme jako datový typ object.
public delegate void MyEventHandler(object sender, MyEventArgs e);
Dále nadefinujeme veřejnou proměnnou s modifikátorem event, která musí být stejného typu jako náš delegát. Tato proměnná představuje událost, jež má být vyvolána. Delegáta použijeme pro určení metody, která bude událost obsluhovat, a také jím určíme, jaké parametry mají být metodě předávány.
Nyní nadeklarujeme vlastní událost pomocí klíčového slova event.
public event MyEventHandler OnMyEventHandler;
Tím je určená vlastní událost. Důvod, proč je celý proces poněkud složitý, je, abychom mohli kontrolovat, zda je událost skutečně deklarována a přiřazena metoda. Pokud ne, událost nás nezajímá a nemusíme pro ni ani vytvářet kód. V následující metodě provedeme tuto kontrolu, inicializujeme instanci události a vyvoláme samotnou událost:
protected void OnMyEvent(object sender, DateTime time, string newValue)
{
MyEventArgs e = new MyEventArgs(time, newValue);
if(OnMyEventHanlder != null)
OnMyEventHandler(sender, e);
}
Zde je kompletní třída:
class MyClass
{
private string strValue;
public delegate void MyEventHandler(object sender, MyEventArgs e);
public event MyEventHandler OnMyEvent;
public MyClass()
{
this.strValue = String.Empty;
}
public string Value
{
get { return strValue; }
set
{
strValue = value;
MyEvent(this, DateTime.Now, value);
}
}
protected void MyEvent(object sender, DateTime time, string newValue)
{
MyEventArgs e = new MyEventArgs(time, newValue);
if (OnMyEvent != null)
OnMyEvent(sender, e);
}
}
Nyní si vytvoříme její instanci, přihlásíme se k odběru události a přiřadíme jí obslužnou metodu:
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.OnMyEvent += new MyClass.MyEventHandler(mc_OnMyEvent);
for (int i = 0; i < 100; i++)
mc.Value = "Hodnota " + i.ToString();
Console.Read();
}
static void mc_OnMyEvent(object sender, MyEventArgs e)
{
Console.WriteLine("Hodnota byla v " + e.Time.ToLongTimeString() + " změněna na " + e.NewValue);
}
Hodnotu změníme stokrát, takže přijmeme sto událostí.
Úkol číslo 3
Zadání
Vytvořte generickou třídu, která bude představovat jednoduchou implementaci funkce třídy List (jednoduchý seznam prvků, základní metody jako Add, Remove, Clear apod.). Interně však nesmíte využívat třídu List. Důležité bude, že každé volání metody Add a Remove vyvolá různé události, které budete v ukázkovém kódu nějak zpracovávat.