Dědičnost

5. díl - Dědičnost

Tomáš Herceg       9. 9. 2009       C#, VB.NET       9046 zobrazení

V tomto díle našeho seriálu se podíváme na to, jak funguje dědičnost a polymorfismus. Ukážeme si rozdíl mezi virtuálními metodami a překrýváním a naučíme se je deklarovat.

V minulém díle jsme se celkem podrobně podívali na třídy, vlastnosti a metody uvnitř tříd. Pro zopakování si zkuste zodpovědět a vyřešit tyto otázky:

  1. Vysvětlete rozdíl mezi hodnotovými a referenčními typy, uveďte příklady těchto typů.
  2. Co je to narrowing a widening conversion? Kde se uplatňují?
  3. Co je to konstruktor a jak se deklaruje?

Ukázkový příklad na procvičení tříd

Abychom nebyli pořád jen teoretici a naučili se také něco praktického, ukážeme si jednu řešenou úlohu. Navíc si na ní vysvětlíme speciální druh vlastnosti, které se říká indexer.

Zadání

Napište třídu MyList, která obsahuje vnitřní pole typu Int32 délky 4, má metody Add a Remove a vlastnost Count. Celá tato třída bude fungovat jako seznam čísel neomezené velikosti.

Metoda Add přidá do seznamu na konec číslo, které jí předáme jako jediný parametr. Hodnoty se pochopitelně ukládají do vnitřního pole. Pokud pole již nestačí a potřebujeme více místa, vytvoříme si nové pole (dvakrát větší) a prvky překopírujeme.

Metoda Remove odebere ze seznamu hodnotu na indexu, který byl uveden jako argument této metody. Čísluje se od nuly. Prvky, které jsou za vymazaným číslem, se posunou o 1 dopředu, aby seznam byl souvislý.

Vlastnost Count vrací počet prvků, které kolekce obsahuje (pozor, nezaměňovat se velikostí vnitřního pole!). Je navenek jen pro čtení, kolekce se nedá smazat tím, že počet položek nastavíme na nulu.

Vzorové řešení

Základní šablona je úplně jasná – uděláme si deklaraci třídy a do ní zapíšeme požadované metody a vlastnosti.

Kód v jazyce Visual Basic .NET
 Public Class MyList

Private list() As Integer

Public ReadOnly Property Count() As Integer
Get

End Get
End Property

Public Sub Add(ByVal value As Integer)

End Sub

Public Sub Remove(ByVal index As Integer)

End Sub

End
Class
Kód v jazyce C#
     public class MyList
{

private int[] list;

public int Count
{
get
{

}
}

public void Add(int value)
{

}

public void Remove(int index)
{

}
}
Inicializace

To je základní struktura naší třídy. Teď již jen doplnit kód. Na začátku má mít naše pole velikost 4. To můžeme napsat rovnou do deklarace proměnné list. Vypadalo by to takto:

Kód v jazyce Visual Basic .NET
     Private list(3) As Integer

Kód v jazyce C#
     private int[] list = new int[4];

Všimněte si, že u počtu prvků ve VB.NET uvádíme nejvyšší index (tedy 3), v C# počet prvků (tedy 4). To je drobný rozdíl a je třeba na něj dávat pozor. Pole se v .NETu vždy indexují od nuly.

Toto řešení našeho problému ovšem není ideální a vřele bych doporučil jej moc nepoužívat. U naší třídy je to skoro jedno, máme zde jen jednu proměnnou. Ale představte si, že by třída měla proměnných patnáct a každou takovou jsme inicializovali hned při její deklaraci. Bude to již dosti nepřehledné a na první pohled nebude zřejmé, co se při vytvoření instance třídy bude vlastně vše dít. Jen doplním, že inicializace vnitřních proměnných tříd a struktur nastává před voláním konstruktoru. Pokud členskou proměnnou třídy či struktury nezinicializujete, bude v ní výchozí hodnota (u číselných datových typů 0, u Booleanu hodnota false, u referenčních typů null).

Kvůli přehlednosti je velmi vhodné dávat všechny inicializace vnitřních proměnných do konstruktoru – je jasné, v jakém pořadí se inicializace provedou, a jsou všechny pohromadě, takže víte, co se při vytvoření instance vlastně všechno děje.

Místo předchozího řešení tedy přidáme do třídy konstruktor (výchozí konstruktor, tedy konstruktor bez parametrů).

Kód v jazyce Visual Basic .NET
     Public Sub New()
ReDim list(3)
End Sub
Kód v jazyce C#
         public MyList()
{
list =
new int[4];
}

Jak se konstruktory deklarují už bychom měli vědět z minula, jak se dimenzuje pole již také. V C# prostě vytvoříme nové pole velikosti 4 a přiřadíme jej do proměnné list (předtím tam byla hodnota null). Ve VB.NET použijeme příkaz ReDim, který udělá to samé – vytvoří nové pole s maximálním indexem 3 (tedy pole velikosti 4) a přiřadí jej do dané proměnné.

Počet položek

Nyní je třeba si něco rozmyslet – co naše třída bude vlastně potřebovat za vnitřní proměnné. Pole pro uchovávání hodnot již máme. Pro přidávání prvků musíme vědět, kolik jich v poli je (kolik míst v poli je obsazených), abychom věděli, kam máme přidat další a jestli pole nepotřebuje zvětšit. Tuto hodnotu nezjistíme z pole, musíme si ji uchovávat v proměnné (zoufalé pokusy stylu projdu pole a najdu první nulové políčko jsou samozřejmě nesmyslné, co když někdo do pole uloží nulu). Uvědomme si, že vlastnost Count žádnou hodnotu nenese, je to pouze mechanismus, jak hodnotu vracet.

Přidáme tedy do naší třídy proměnnou a naimplementujeme rovnou vlastnost Count, aby vracela hodnotu této proměnné.

Kód v jazyce Visual Basic .NET
     Private _count As Integer

Public ReadOnly Property Count() As Integer
Get
Return _count
End Get
End Property
Kód v jazyce C#
         private int count;

public int Count
{
get
{
return count;
}
}

To by byla ta nejjednodušší část. Proměnnou _count, resp. count nemusíme inicializovat, automaticky je inicializována na nulu. Všimněte si, že ve VB.NET je nutné do názvu proměnné dát podtržítko, protože Visual Basic nerozlišuje mezi velkými a malými písmeny. Count je název vlastnosti a proměnná se tedy nemůže jmenovat stejně. Jazyku C# to nijak nevadí, proto tam podtržítko obvykle nedáváme.

Upozornění: Pokud byste psali nějakou knihovnu v C#, je dobré dodržovat konvence pro pojmenování (více se o nich dočtete například v článku pana Linharta Konvence při psaní zdrojového kódu) a rozhodně nedělat v rámci jedné třídy veřejné členy s názvy lišícími se pouze velikostí písmen. I když ve VB.NET nepíšete, počítejte s tím, že někdo jiný ano a nepřidělávejte jim zbytečně problémy (oni jich mají i tak dost). Jde pouze o veřejné členy, pokud je jeden z takových členů internal nebo private, tak to až tak nevadí.

Přidávání prvků

Nyní napíšeme přidávání prvků. Je třeba si rozmyslet, co vlastně metoda má dělat. Nejprve se podíváme, jestli naše pole nepotřebuje zvětšit. Pokud počet prvků je roven velikosti našeho pole, budeme zvětšovat.

Jak zvětšit pole? Pamětníci možná vzpomenou ve VB.NET na příkaz ReDim Preserve, který uměl “zvětšit nebo zmenšit” pole a zachovat v něm hodnoty. Na tento příkaz zapomeňte, uděláme to jinak a pořádně (on ten příkaz vlastně dělá úplně to samé).

Vytvoříme si novou proměnnou s novým polem, dvakrát větším. Pomocí statické metody Array.Copy prvky překopírujeme (šlo by to i For cyklem, ale ať nepíšeme to, co už je, použijeme již hotovou metodu). Pak jen toto nové pole do proměnné list přiřadíme.

A nezapomeneme přidat ten nový prvek a zvětšit počet. Pojďme tedy napsat kód.

Kód v jazyce Visual Basic .NET
     Public Sub Add(ByVal value As Integer)
'zjistit, jestli nemáme pole zvětšit
If _count = list.Length Then
'je třeba zvětšit pole
Dim newList(list.Length * 2 - 1) As Integer 'vytvoříme nové dvakrát větší
Array.Copy(list, newList, list.Length) 'zkopírujeme daný počet prvků z prvního do drhého pole
list = newList 'pole vyměníme
End If

'přidat prvek na konec a zvýšit počet
list(_count) = value
_count += 1
End Sub
Kód v jazyce C#
         public void Add(int value)
{
//zjistit, jestli nemáme pole zvětšit
if (count == list.Length)
{
//je třeba zvětšit pole
int[] newList = new int[list.Length * 2]; //vytvoříme nové dvakrát větší
Array.Copy(list, newList, list.Length); //zkopírujeme daný počet prvků z prvního do drhého pole
list = newList; //pole vyměníme
}

//přidat prvek na konec a zvýšit počet
list[count] = value;
count++;
}

Pokud se počet obsazených prvků v poli od začátku (tedy _count resp. count) rovná délce pole (každé pole má vlastnost Length, která vrací počet prvků v poli, tentokrát stejně pro VB.NET i C#), je pole již plné a musíme jej zvětšit. Jakmile je zvětšené, na pozici count (pokud je obsazených 5 prvků, tak tedy na pozici 5, což je šesté políčko, číslujeme totiž od nuly) se uloží nová hodnota a počet se zvýší o jedničku.

Teď k samotnému zvětšení pole – vytvoříme si proměnnou newList a do ní dáme pole integerů velikosti dvojnásobku délky aktuálního pole, tedy list.Length * 2. Ve VB.NET odečteme ještě jedničku, protože chceme index posledního políčka. Tím se na haldě vytvořilo nové pole a v proměnné newList máme referenci na toto pole.

Metodou Array.Copy, která bere 3 parametry (původní pole, nové pole a počet kopírovaných prvků) zkopírujeme položky ze starého pole do pole nového. Tato metoda má samozřejmě více přetížení, v některém z nich je možné i říct, od jakého indexu se má číst ve zdrojovém poli a od kterého indexu se má zapisovat do cílového pole, ale to my teď nechceme. Kopírujeme tolik prvků, jaká je velikost pole list.

Nakonec referenci na nové pole přiřadíme do proměnné list, čímž pádem odteď přes proměnnou list pracujeme s novým zvětšeným polem. Upozorňuji, že jakmile v .NETu jednou pole vytvoříte, už jej nezvětšíte ani nezmenšíte. Musíte vytvořit nové a překopírovat jej. Proč nezvětšujeme pole při vložení nového prvku pokaždé, ale jen někdy? Kvůli efektivitě. Pokud pole zvětšujeme na dvojnásobek této kapacity, je to efektivní, protože čím je prvků více, tím méně často se musí kopírovat do většího pole. Rozhodně je to mnohem efektivnější, než kopírovat celé pole s milion prvky pokaždé. Pravý důvod spočívá v pojmu amortizovaná složitost, ale do nich bych teď nerad zabíhal. Více si o nich povíme v některém z příštích dílů, až budeme mít na programu kolekce.

Vyvstává otázka, co se stane se starým polem při tomto přiřazení. Před ním byla v proměnné list reference na původní “malé” pole a v proměnné newList reference na nové “velké” pole. Po přiřazení obě proměnné obsahují referenci na “velké” pole a po vyskočení z podmínky se proměnná newList zruší, protože jsme vystoupili z bloku, kde byla deklarována. Co se stane s původním “malým” polem?

Odpověď je zdánlivě prostá – “nic”. Ve skutečnosti je správnější říci “zatím nic”. Na staré “malé” pole neexistuje v žádné proměnné reference, kdybychom s ním chtěli něco dělat, tak nemůžeme, nemáme proměnnou, přes níž bychom se k tomuto poli dostali. Pole normálně zůstane na haldě a bude tam chvíli strašit. Do doby, kdy se takových “mrtvých” objektů nenajde víc – objektů, na něž již neexistuje žádná reference. Až nastane pravý čas (až si aplikace naalokuje pár dalších bloků paměti), spustí se Garbage Collector, který takovéto objekty nebo skupiny vzájemně provázaných objektů, na něž z žádných dostupných proměnných neexistuje reference, najde a odalokuje. Tím v paměti vzniknou volná místa a GC provede ještě defragmentaci – vezme celou haldu a jakoby ji “sesype” tak, aby se volná místa zaplnila. To znamená dost složitou operaci, při kterých je nutné za běhu vyměnit reference v proměnných. Díky mnoha režijním informacím, které se za běhu .NET aplikace v paměti uchovávají, to však je možné a díky tomu to funguje tak, jak má.

Dealokace nepoužívané paměti trvá trochu déle a je dost náročná, všechna vlákna aplikace se během této doby pozastaví. Výhodou je naproti tomu to, že při požadavku na přidělení nové paměti se nemusí na haldě hledat volné místo, jako tomu bývá u unmanaged aplikací. Tam většina režie spočívá v hledání volného místa v paměti, v .NETu naopak většina režie spočívá až v dealokaci nepotřebné paměti. Je dobré si tento rozdíl uvědomovat a pamatovat na něj.

Odebrání položky

Odebírání položky bude trochu jednodušší. Nemusíme tam totiž řešit zmenšování pole, stačí pouze daný prvek vyhodit a posunout prvky, které byly za ním, o jedno políčko vlevo. I když pole vyprázdníme, použijeme tzv. Luciferův princip – “Co peklo jednou schvátí, to už nenavrátí.”

Jak prvky posunout? Nyní už bych na metodu Array.Copy nespoléhal, nikde není definováno, že prvky kopíruje zepředu dozadu (je možné, že kvůli nějakým optimalizacím kopíruje pole třeba pozpátku). Pokud bychom prvky kopírovali pozpátku, nefungovalo by to, protože bychom si přepisovali prvky, které jsme ještě neposunuli. Použijeme tedy for cyklus.

Kód v jazyce Visual Basic .NET
     Public Sub Remove(ByVal index As Integer)
'posunout prvky o políčko zpět
For i As Integer = index To list.Length - 2
list(i) = list(i + 1)
Next
'snížit počet
_count -= 1
End Sub
Kód v jazyce C#
         public void Remove(int index)
{
//posunout prvky o políčko zpět
for (int i = index; i < list.Length - 1; i++)
list[i] = list[i + 1];
// snížit počet
count--;
}
Indexer

Co že tohle slovíčko vlastně znamená? Jde o speciální vlastnost, která umožňuje přistupovat k položkám třídy pomocí indexů, například čísel, ale mohou to být klidně i řetězce, objekty či cokoliv jiného. Jde nám o to, abychom mohli číst a zapisovat prvky našeho seznamu – ve VB.NET přes kulaté závorky, v C# přes závorky hranaté.

Dělá se to trochu odlišně ve VB.NET a v C#, proto si to rozebereme podrobně pro oba jazyky zvlášť.

Kód v jazyce Visual Basic .NETVisual Basic .NET umí na rozdíl od C# jakékoliv vlastnosti přidat parametry. Toto je například vlastnost Hello, která má dva argumenty – číslo a řetězec. Tyto argumenty můžeme samozřejmě používat v getteru i v setteru a dělat si s nimi, co uznáme za vhodné.

     Public Property Hello(ByVal i As Integer, ByVal s As String) As String
Get

End Get
Set(ByVal value As String)

End Set
End Property

Abychom tedy v našem seznamu mohli přiřazovat hodnoty a číst hodnoty na určitém indexu, přidáme do naší třídy vlastnost Items.

     Public Property Item(ByVal index As Integer) As Integer
Get
Return list(index)
End Get
Set(ByVal value As Integer)
list(index) = value
End Set
End Property

Pokud bychom měli proměnnou seznam s instancí naší třídy MyList, pak bychom k pátému prvku seznamu přistupovali přes seznam.Item(4). Ale co když chceme se seznamem opravdu pracovat jako s polem a pátý prvek chceme mít na seznam(4)?

Visual Basic má pro tento účel tzv. výchozí vlastnost. Jedné vlastnosti ve třídě můžeme do deklarace přidat klíčové slovo Default, které zajistí, že pokud název vlastnosti vynecháme, bude se přesto s touto vlastností pracovat. Je nutno podotknout, že toto platí pouze pro vlastnosti, které mají parametry (u bezparametrických by se nepoznalo, jestli pracujeme přímo s instancí třídy anebo s výchozí vlastností).

Naší vlastnosti Item tedy do deklarace přidáme klíčové slovo Default, čímž pádem se z ní stane výchozí vlastnost a zápis seznam(4) bude ekvivalentní zápisu seznam.Item(4). Jak prosté, milý Watsone.

Kód v jazyce Visual Basic .NET
     Default Public Property Items(ByVal index As Integer) As Integer
Get
Return list(index)
End Get
Set(ByVal value As Integer)
list(index) = value
End Set
End Property

Kód v jazyce C#Jak je to ale v C#? Bohužel, jazyk C# nepodporuje vlastnosti s parametrem. Umí pouze věc, které se říká indexer. To je speciální vlastnost bez názvu, která může mít parametry. Deklaruje se tak, že jakoby místo názvu vlastnosti uvedete klíčové slovo this a přidáte za něj seznam parametrů. V getteru a případně setteru pak používáte hodnoty argumentů. Nevýhoda je, že v C# nemůžete mít parametry u jakékoliv vlastnosti, ale pouze u indexeru, což je zkrátka omezení – někdy se to hodí (i když vždy se dá vyjít i bez toho). Jak se tedy indexer deklaruje?

Kód v jazyce C#
         public int this[int index]
{
get { return list[index]; }
set { list[index] = value; }
}

Jediná možnost, jak k položkám v seznamu přistupovat, je seznam[4]. Žádné seznam.Item[4] zkrátka nefunguje, indexer je v C# vlastnost bez názvu, která umožňuje přistupovat k vnějším prvkům třídy pomocí indexu. U VB.NET je mechanismus vlastností s argumenty a výchozích vlastností (obyčejná vlastnost v C# argumenty mít nemůže) zkrátka o něco silnější.

Co nám ještě chybí?

Jako ukázkový příklad na to, co chceme procvičovat, by nám to stačilo. Chybí toho ale ještě hodně. Protože toto je pouze ukázkový příklad, záměrně jsme náš seznam naučili jen základní metody a vlastnosti. Aby to bylo naprosto správné, měl by seznam umět daleko více metod a ideálně implementovat rozraní IList. Co jsou to rozhraní, to se dozvíme časem.

Dále by to chtělo proměnným, vlastnostem, metodám a samotné třídě přidat dokumentační komentáře. Ty se zobrazují v nápovědě IntelliSense a dá se z nich generovat vývojářská dokumentace (takhle je třeba dělaná MSDN Library). Mají mnoho výhod, ale povíme si o nich, až toho budeme umět trochu víc.

V neposlední řadě bychom měli například u metody Remove kontrolovat, jestli je index platný (jestli například není záporný a jestli není větší nebo roven počtu prvků v poli). V případě, že je argument nesprávný, měli bychom vyhodit výjimku. Vzhledem k tomu, že o výjimkách se budeme bavit o několik dílů dále, nebudeme si to ukazovat již teď.

Bylo by zbytečné ale seznam dodělávat celý, protože by nám to vydalo na celý článek a navíc .NET Framework již přesně takový seznam obsahuje. Není ale na škodu umět si ho napsat sám.

 

Zpět k teorii aneb jak je to s tou dědičností

Jako programátoři, kteří znají nějaký programovací jazyk, byste měli alespoň tušit, co to dědičnost je. Je jasné, že v každé technologii máme k dispozici trochu jiné prostředky a možnosti, základní princip je ovšem stejný. My se teď zaměříme na to, jak to funguje v .NET Frameworku.

Zjednodušeně řečeno pokud třída A dědí ze třídy B, pak má automaticky všechny vnitřní proměnné, vlastnosti, metody atd. ze třídy B. Tuto sadu zděděných členů může rozšířit o členy nové, případně může přepsat / překrýt vnitřní implementaci některých členů zděděných. Nemůžeme ovšem žádné zděděné členy odstranit.

Funguje zde princip zvaný polymorfismus – třídu A můžeme přiřadit do proměnné typu B a pracovat s ní jako s datovým typem B. Máme totiž zaručeno, že třída A má všechny členy, které měla třída B. I když ve třídě A změníme jejich vnitřní implementaci, navenek je jejich deklarace stejná (pořád je to například metoda se dvěma parametry typu integer).

.NET Framework nepodporuje vícenásobnou dědičnost, jako třeba C++. Tento nedostatek je vyvážen principem rozhraní (interfaces), o němž si budeme povídat později. Nyní však k samotné dědičnosti, začneme jednoduchým příkladem. Kde se dá dědičnost použít?

Typickým příkladem použití dědičnosti je tzv. ISA hierarchie (ISA je zkratka z anglického is a). Mějme například třídu Feline (kočkovité šelmy) a dále třídy Cat (kočka), Lion (lev) a Cheetah (gepard), které ze třídy Feline dědí. Zde funguje ISA hierarchie – platí, že “cat is a feline” – kočka je kočkovitá šelma. Stejně tak lev i gepard.

Co navíc každá kočkovitá šelma umí? Určitě umí zařvat, když chce někoho zastrašit. Většina kočkovitých šelem, které nezkazil člověk, řve “Roar!” Kočka domácí, jež dlouhá staletí žila u člověka, tuto schopnost pozbyla a umí akorát vydávat zvuk “Meow!”. Jak toto převést do kódu?

Kód v jazyce Visual Basic .NET
 Public Class Feline

Public Sub Sound()
Console.WriteLine(
"Roar!")
End Sub

End
Class

Public
Class Cat
Inherits Feline

End Class

Public
Class Lion
Inherits Feline

End Class

Public
Class Cheetah
Inherits Feline

End Class
Kód v jazyce C#
     public class Feline
{
public void Sound()
{
Console.WriteLine("Roar!");
}
}

public class Cat : Feline
{

}

public class Lion : Feline
{

}

public class Cheetah : Feline
{

}

Máme zde deklaraci 4 tříd – Feline, Cat, Lion a Cheetah. Třída Feline obsahuje metodu Sound, která má za úkol vydat zvuk daného zvířete. V našem případě pomocí statické metody Console.WriteLine vypíšeme na konzoli text “Roar!”. Protože lev i gepard opravdu takto řvou, nemusíme tam nic měnit. U kočky bychom ale chtěli, aby metoda Sound vypisovala hlášku “Meow!”.

Jsou dva způsoby, jak to zařídit. Ten první se skoro nepoužívá, ale je dobré o něm vědět, občas nám totiž může stačit. V praxi jsem jej použitý viděl jen dvakrát a jen u jednoho případu bych připustil, že to mělo své opodstatnění.

Překrývání (shadowing)

Do třídy Cat přidáme metodu Sound s klíčovým slovem Shadows ve VB.NET resp. new v C#. Více je to vidět na příkladu.

Kód v jazyce Visual Basic .NET
     Public Shadows Sub Sound()
Console.WriteLine(
"Meow!")
End Sub
Kód v jazyce C#
         public new void Sound()
{
Console.WriteLine("Meow!");
}

Pokud klíčové slovo Shadows resp. new zapomeneme, bude to fungovat stejně, ale dostaneme od kompilátoru varování.

Jak tohle celé funguje? Je nutné si uvědomit, že určování, která metoda se zavolá, tedy jestli se vypíše “Roar!” nebo “Meow!”, probíhá už za kompilace a řídí se datovým typem proměnné (pozor, neřídí se to typem objektu, který v proměnné skutečně je). Následující příklad by měl názorně demonstrovat (tedy “odpříšerovat”), jak to celé funguje.

Kód v jazyce Visual Basic .NET
         Dim c As New Cat()
c.Sound()
' vypíše "Meow!"
Dim f As Feline = New Cat()
f.Sound()
' i když je ve f2 kočka, vypíše "Roar!", řídíme se totiž typem proměnné f
Kód v jazyce C#
             Cat c = new Cat();
c.Sound();
// vypíše "Meow!"

Feline f = new Cat();
f.Sound();
// i když je ve f2 kočka, vypíše "Roar!", řídíme se totiž typem proměnné f

Toto chování je vpravdě relativně nevhodné. Většinou potřebujeme rozhodovat, která metoda se zavolá, až za běhu podle typu objektu, který v proměnné je skutečně uložen, než za kompilace podle typu proměnné. To je také důvod, proč se překrývání příliš nepoužívá. Tento problém řeší použití virtuálních metod.

Virtuální metody

Při volání instančních metod se za standardních okolností již v době kompilace určí adresa metody, kam se má pro zavolání dané metody skočit. Nevýhodu tohoto postupu jsme viděli výše. Vzhledem k tomu, že se dané určení metody provádí již během kompilace, jediná informace, kterou se kompilátor může řídit, je datový typ proměnné. Co v proměnné bude přesně za hodnotu kompilátor samozřejmě nemůže tušit.

Můžeme ale využít toho, že assembly si nese režijní informace o každém datovém typu, který používá. Každý objekt na haldě si navíc nese odkaz na svůj datový typ (který může být samozřejmě jiný než datový typ proměnné, přes kterou k objektu přistupujeme). Každý datový typ má mimo jiné tzv. tabulku virtuálních metod, což je jakýsi seznam adres virtuálních metod, do něhož se runtime podívá před voláním metody a zjistí, kde metoda je. Zjednodušené schéma volání je vidět na následujícím obrázku:

Princip volání virtuální metody

Při zavolání metody Sound() se tedy runtime podívá do tabulky virtuálních metod daného objektu a dohledá metodu Sound() s danými parametry, díky čemuž se až za běhu určí konkrétní metoda, která se zavolá.

Jak virtuální metody použít v kódu? Změny musíme udělat jak v rodičovské, tak i ve dceřinné třídě. Abyste tedy mohli používat virtuální metody, musí mít podporu i na straně předka. Jak tedy mají metody vypadat v třídě předka? V deklaraci jim přibude klíčové slovo Overrideble resp. virtual.

Kód v jazyce Visual Basic .NET
     Public Overridable Sub Sound()
Console.WriteLine(
"Roar!")
End Sub
Kód v jazyce C#
         public virtual void Sound()
{
Console.WriteLine("Roar!");
}

Ve třídě Cat pak musíme metodu overridovat (česky správný překlad neznám, ale budu používat termín přepsat, přijde mi to asi nejvýstižnější). V hlavičce metody v poděděné třídě pak musíte přidat klíčové slovo Overrides resp. override.

Kód v jazyce Visual Basic .NET
     Public Overrides Sub Sound()
Console.WriteLine(
"Meow!")
End Sub
Kód v jazyce C#
         public override void Sound()
{
Console.WriteLine("Meow!");
}

Pokud se podíváme na příklad použití, bude se to nyní chovat jinak než v předchozím případě. Metoda, která se zavolá, se bude opravdu rozhodovat až za běhu.

Kód v jazyce Visual Basic .NET
         Dim c As New Cat()
c.Sound()
' vypíše "Meow!"
Dim f As Feline = New Cat()
f.Sound()
' protože hodnota je typu Cat, vypíše opět "Meow!"
Kód v jazyce C#
             Cat c = new Cat();
c.Sound();
// vypíše "Meow!"

Feline f = new Cat();
f.Sound();
// i protože hodnota je typu Cat, vypíše opět "Meow!"

Závěrem

V tomto díle jsme si vysvětlili základy dědičnosti, v příštím se podíváme na abstraktní členy a nakousneme rozhraní.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

7. Rozhraní 19. 7. 2010
6. Dědičnost - dokončení 1. 1. 2010
5. Dědičnost 9. 9. 2009
4. Třídy 9. 6. 2009
3. Datové typy 5. 5. 2009
2. Základní elementy VB.NET a C# 18. 4. 2009
1. Úvod do .NET Frameworku 3. 4. 2009

 

Mohlo by vás také zajímat

Windows Presentation Foundation (WPF) - díl 7.: Grid

Grid je jedna z nejdůležitější a nejpoužívanějších pozicovacích komponent ve WPF. Ulehčuje návrh formulářů a své uplatnění nachází v řadě scénářů.

Windows Azure - díl 6.: Migrace ASP.NET aplikace do Azure WebSite

Dnes si ukážeme, jak zmigrovat existující ASP.NET aplikaci a databázi z webhostingu do Windows Azure.

ASP mvc–from zero to hero (3), vývojový stack a solution

 

 

Nový příspěvek

 

Diskuse: Dědičnost

Pro slovo override má čestina celkem pěkný výraz "přetížit".

nahlásit spamnahlásit spam 0 odpovědětodpovědět

To bohužel nemá, přetížit znamená něco jiného a překládá se jím slovo overload.

Přetížení (overload) je např. situace, kdy máme víc metod, které se jmenují stejně, ale mají každá různý počet nebo různé typy parametrů. Přetěžovat se dají také operátory.

Přepsání (override) je úprava chování metody v poděděné třídě.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Chtěl jsem se jen zeptat,zda bude seriál pokračovat? Hodil by se díl o rozhraní,apod.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Také prosím o pokračování. Je to skvěle podaný a lechce pochopitelný kurz.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Také bych rád pokračovaní, styl vysvětlování a popis problémů je velice pěkný a spousta informací se z článků dá vytěžit. Předem děkuji.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Dědičnost

A doufam ze budete pokracovat. Dik

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Dědičnost

Kvuli 1 prvku kopirovat pole prvku? No fuj tomu rikam optimalizace :)!!

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Pokud tu hlášku nemyslíte jako vtip, tak jste si evidentně článek nepřečetl. Ono je to tam vysvětleno a popsáno, proč se to dělá takhle.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback