Dědičnost - dokončení

6. díl - Dědičnost - dokončení

Tomáš Herceg       01.01.2010       C#, VB.NET, .NET       17373 zobrazení

V tomto díle doděláme resty z minula - dokončíme dědičnost, ukážeme si volání konstruktorů předka, předávání parametrů referencí a hodnotou, návratové parametry a volitelné argumenty u metod. Dále si popíšeme abstraktní metody a třídy a v neposlední řadě statické a parciální třídy.

Po několikaměsíční odmlce je na řadě další díl seriálu .NET Framework od začátku. V minulých dílech jsme si poměrně podrobně popsali datové typy, třídy a dědičnost, máme ještě ale nějaké resty.

Co bychom měli nyní vědět?

  • Jaký je rozdíl mezi hodnotovými a referenčními typy?
  • Co je to narrowing a widening conversion?
  • Co je to konstruktor a jak volat jeden konstruktor z jiného?
  • Co jsou to virtuální metody a jaký je rozdíl mezi virtuální metodou a překrýváním (shadowingem)?

Pokud v některých věcech tápete, podívejte se do předchozích dílů tohoto seriálu, ať máte jistotu, že jste se neztratili. Znalost těchto pojmů je velmi důležitá.

Z minulých dílů máme ještě nějaké resty, takže ještě než začneme s rozhraními, podíváme se na ně.

Deklarace metod a nepovinné parametry

Visual Basic je jazyk s poměrně dlouhou historií a již velmi dlouhou dobu podporuje nepovinné parametry u metod. Tím se C# chlubit nemůže, podpora pro volitelné parametry bude až od verze .NET Frameworku 4 (v době psaní článku ve verzi Beta 2). Ono se dá konec konců žít i bez této vlastnosti, stačí pro každou kombinaci parametrů přidat přetížení, což je sice otrava, ale má to své výhody.

Takto se deklaruje metoda s volitelnými parametry ve VB.NET. Volitelné parametry před sebou mají klíčové slovo Optional a musí být všechny až za parametry povinnými (aby se případně daly vynechat a bylo jasné, který je který).

Kód v jazyce Visual Basic .NET
     Public Sub Metoda(ByVal povinny As Integer, Optional ByVal volitelny As String = "hello", _
Optional ByVal volitelny2 As Integer = 15)

End Sub

Volání metody může pak vypadat jedním z následujících způsobů. Buď volitelné parametry nezadáme vůbec a dáme jen povinné, nebo zadáme povinné a několik prvních volitelných (klidně i všechny). Pokud chceme třeba zadat jen povinný parametr a druhý z volitelných, použijeme specifikaci parametru (jak ukazuje poslední řádek).

Kód v jazyce Visual Basic .NET
         Metoda(1)       'pro volitelné se použijí výchozí hodnoty
Metoda(
1, "text") 'pro druhý volitelný se použije výchozí hodnota
Metoda(
1, volitelny:="text", volitelny2:=14) 'specifikace názvů
Metoda(
1, volitelny2:=14) 'specifikace konkrétního parametru

V C# toto možné není a je nutné použít přetěžování metod.

Kód v jazyce C#
         public void Metoda(int povinny, string volitelny, int volitelny2)
{
//kód metody
}

public void Metoda(int povinny, string volitelny)
{
Metoda(povinny, volitelny,
15);
}

public void Metoda(int povinny, int volitelny2)
{
Metoda(povinny,
"hello", volitelny2);
}

public void Metoda(int povinny)
{
Metoda(povinny,
"hello", 15);
}

Při přetěžování metod (což je deklarace metody se stejným názvem ale různými počty nebo druhy parametrů) se do všech přetížení kromě toho se všemi argumenty umístí jen kód, který doplní nezadané parametry jako výchozí hodnoty a zavolá to “vyvolené” přetížení s největším počtem parametrů, které provede onu “špinavou práci”. Díky tomu nemáme nikde opakující se kód, který se v budoucnu může změnit.

Není samozřejmě nutné deklarovat všechna možná přetížení, stačí jen ta, která budeme používat. Je potřeba dát pozor při deklaraci přetížení se stejným počtem parametrů – jejich datové typy se musí lišit, aby kompilátor v době kompilace poznal, které konkrétní přetížení se má vybrat. Nestačí, když se dvě přetěžované metody liší návratovým typem, musí se lišit typem alespoň jednoho argumentu. Vždy se použije to přetížení, které potřebuje nejméně automatických konverzí, pokud je takových víc, kompilátor vyhlásí chybu kompilace.

Kontrolní otázka

Mějme tato dvě přetížení metody Metoda.

Kód v jazyce C#
         public void Metoda(int parametr)
{
}

public void Metoda(float parametr)
{
}

Zkuste si nejprve sami určit, co se stane, když použijeme tato volání:

Kód v jazyce C#
             Metoda(15);
Metoda(
15f);
Metoda((
long)15);
Metoda((
byte)15);

První a druhé případy jsou asi jasné – 15 je int a tudíž se zavolá první přetížení, 15f je float a tudíž se zavolá druhé. Typ long nemá implicitní konverzi na int, ale má konverzi na float, takže se zavolá to druhé. Poslední případ trochu překvapil i mně – čekal jsem, že kompilátor vyhodí chybu, protože byte má implicitní konverzi jak na int, tak i na float. Ve specifikaci C# je ale definována tzv. “lepší konverze” (better conversion) a je tam seznam výjimek, který tohle má na starosti. Je to logické, kompilátor si vybere typ, který je blíž a kde hrozí menší ztráta přesnosti. Ve VB.NET to funguje stejně. Je trochu nepříjemné, že není jedno triviální obecné pravidlo a že jsou si některé implicitní konverze rovnější. Tyhle výjimky a speciální případy jsou důvodem, proč mají specifikace jazyků stovky stránek namísto desítek.

O to horší je, že když si nadeklaruji vlastní automatickou konverzi, nemůžu nijak ovlivnit její prioritu. V obecném případě pokud vytvoříte třídy A, B a C, třídě C nadefinujete automatickou konverzi na A i na B, dále vytvoříte přetížení pro parametr typu A i B a na závěr tuto metodu zavoláte s typem C, kompilátor ohlásí, že neví, které přetížení si má vybrat, protože C umí “za stejnou cenu” konvertovat na A i na B. Nemůžete jednu konverzi upřednostnit před druhou, to funguje jen u vestavěných datových typů. Více ve specifikaci.

Jak se deklarují automatické konverze, k tomu se dostaneme později, až budeme mít operátory. Na duhou stranu, tohle je taková chuťovka - při běžném používání člověk nenarazí na problém. Stačí se spokojit s tím, že překladač vybere to nejbližší přetížení, které potřebuje nejméně konverzí, anebo vyhodí chybu, pokud neví, co s tím. Funguje to intuitivně a přirozeně.

Předávání argumentů hodnotou a referencí

Co se stane, když zavoláme nějakou metodu? Na zásobníku se vytvoří její aktivační záznam. Ten obsahuje jednak všechny lokální proměnné dané metody, návratovou adresu (kde se má po vykonání metody pokračovat v provádění) a hodnoty předaných argumentů. Připomínám, že hodnotou proměnné či parametru je v případě hodnotového datového typu samotná hodnota, v případě referenčního datového typu pak reference odkazující na data objektu na haldě.

Jakmile je aktivační záznam vytvořen, spustí se kód dané metody. Při opouštění metody (jakýmkoliv způsobem, ať už přes klíčové slovo return, nebo přes výjimku) se její aktivační záznam zase odstraní, před tím se “procesor” podívá na návratovou adresu, aby věděl, kde pokračovat. Připomínám, že zásobník je datová struktura, kde se přidává a odebírá z toho samého konce, takže to, co jsme přidali naposledy, odebereme nejdříve.

Při předávání hodnoty do metod se standardně předávané argumenty kopírují, čemuž se říká předávání hodnotou, nebo anglicky by value. Pokud je argument hodnotového typu, zkopíruje se hodnota, pokud je typu referenčního, zkopíruje se reference (přičemž ale objekt na haldě zůstane pořád jenom jeden). Takové chování se nám v jistých situacích nehodí – občas potřebujeme z volané metody změnit nějaké proměnné v metodě volající.

V takovém případě můžeme chtít argumenty metody předávat referencí, anglicky by reference. Pak se jako argument metody nepředá hodnota proměnné, ale reference na proměnnou (což je obyčejná adresa v paměti, kde se nachází hodnota této proměnné). Díky tomu, že máme odkaz na místo, kde je uložena původní hodnota proměnné (což bývá většinou někde na zásobníku), můžeme její hodnotu změnit. Při předávání hodnotou by se změnila jen kopie, která se při opuštění metody zase odstraní.

V jistých situacích můžeme chtít tzv. výstupní argumenty, což v praxi znamená, že metodě předáme referencí proměnnou a metoda nám do proměnné něco uloží. Ve VB.NET se tohle vůbec nerozlišuje od standardního předávání referencí, v C# ano, protože C# požaduje, aby každá proměnná před prvním použitím byla inicializována (aby do ní někdo přiřadil hodnotu). Pokud proměnnou předáváme do metody a očekáváme, že ji zinicializujeme v metodě, řekneme v C#, že chceme použít výstupní argument a kompilátor nebude trvat na tom, abychom do proměnné před předáním do metody, tedy před prvním použití proměnné, něco přiřazovali.

Rozdíl mezi předáváním hodnotou je vidět na následujícím příkladu.

Kód v jazyce Visual Basic .NET
     Public Sub Hodnotou(ByVal a As Integer)
a =
15 'změna se neprojeví, a je kopie původní proměnné
End Sub

Public Sub Referenci(ByRef a As Integer)
a =
20 'změna se projeví, a je reference na předanou proměnnou
End Sub
         Dim a As Integer = 10

Console.WriteLine(a) 'vypíše 10
Hodnotou(a)
Console.WriteLine(a) 'vypíše 10
Referenci(a)
Console.WriteLine(a) 'vypíše 20

Kód v jazyce C#

         public void Hodnotou(int a)
{
a =
15; // změna se neprojeví, a je kopie původní proměnné
}

public void Referenci(ref int a)
{
a =
20; // změna se projeví, a je reference na předanou proměnnou
}
             int a = 10;

Console.WriteLine(a); // vypíše 10
Hodnotou(a);
Console.WriteLine(a); // vypíše 10
Referenci(ref a);
Console.WriteLine(a); // vypíše 20

Ve VB.NET u argumentu metody naznačíme, že se předává referencí, klíčovým slovem ByRef místo výchozího ByVal, které nám tam Visual Studio doplňuje automaticky. Při volání metody zapíšeme argumenty tak, jako obvykle. Referencí nemůžeme předávat jakýkoliv výraz, ale pouze proměnnou nebo vlastnost, zkrátka něco, do čeho se dá přiřadit.

V C# je situace trochu jiná. Pokud předáváme parametr referencí, jak v deklaraci metody, tak v jejím volání, musíme před argument dát klíčové slůvko ref. Je doufám jasné, že každý parametr můžeme předávat různým způsobem – jeden hodnotou, druhý referencí, třetí jako výstupní atd.

Výstupní argumenty

Tyto argumenty se nám hodí, pokud chceme metodu, která vrací více než jednu hodnotu. To se dá řešit buď tím, že metoda vrátí nějaký objekt či strukturu (což je mnohdy lepší řešení), anebo bude mít výstupní argumenty. Výstupní argumenty se nepoužívají příliš často, já osobně je moc rád nemám, protože zavolání i s deklarací proměnných zabere víc řádků.

Kód v jazyce Visual Basic .NET

     Public Sub Nastav(ByRef x As Integer, ByRef y As Integer)
x =
10
y =
15
End Sub
     Dim x, y As Integer
Nastav(x, y)

Kód v jazyce C#

     public void Nastav(out int x, out int y)
{
x =
10;
y =
15;
}
     int x, y;
Nastav(
out x, out y);

Ve VB.NET použijeme klasické předávání referencí, v C# musíme použít místo ref klíčové slůvko out. Pokud bychom použili ref, kompilátor by se vztekal, že předáváme do metody proměnnou, do které jsme ještě nic nepřiřadili. Pokud tam dáme out, bude kompilátor vědět, že na její hodnotě nezáleží, že metoda do ní jen přiřadí. Visual Basicu je to jedno, tam to kompilátor nekontroluje a proměnné jsou automaticky vynulovány, takže tento problém odpadá.

Co když předávám referenční datový typ referencí?

Na první pohled to vypadá složitě, ale ve skutečnosti je to velmi jednoduché – pokud předám referencí například pole jakožto referenční typ, pochopitelně se toto pole nebude v paměti kopírovat (nekopírovalo by se ani kdybychom ho předali hodnotou, to už víme).

Pokud ve volané metodě řekněme do pole přiřadím na pátou pozici nějakou hodnotu, změnu uvidí i v metoda volající (opět by ji viděla i kdybychom pole předali hodnotou, obě proměnné ukazují na tentýž objekt na haldě). Do proměnné ale můžeme přiřadit pole úplně jiné. Protože jsme pole předali referencí, vymění se pole i ve volající metodě (což by se v případě předávání hodnotou nestalo).

To samé bude platit i pro objekt – přistoupíme-li na některou jeho vlastnost, změna se projeví jak při předání hodnotou, tak při předání referencí. Předávat referenční typy referencí má tedy smysl pouze v případě, kdy chceme ve volané metodě vyměnit v proměnné volající metody objekt za nějaký jiný. Osobně jsem předávání referenčních typů v praxi viděl jen párkrát, není to příliš používaná technika a jediné rozumné využití této eskapády jsem ještě neviděl. Je to ale vcelku názorný příklad, jak si ujasnit rozdíl mezi referenčními typy a předáváním referencí, což jsou dvě v principu sice podobné, ale obecně naprosto odlišné věci.

Volání konstruktorů předka

V některém z minulých dílů jsme si ukazovali, jak třídě dát víc konstruktorů s různým počtem parametrů a jak z jednoho konstruktoru volat jiný. Pokud používáme dědičnost, velmi často potřebujeme z konstruktoru zavolat konstruktor předka. Tím spíš, pokud rodičovská třída nemá bezparametrický konstruktor, v takovém případě, pokud z této třídy dědíme, dokonce musíme zavolat z každého konstruktoru některý z konstruktorů předka explicitně. Pokud předek má bezparametrický konstruktor, volá se tento konstruktor automaticky před spuštěním konstruktoru potomka, aniž bychom ho museli volat ručně (to však neznamená, že nemůžeme).

Předchozí odstavec mohl být možná trochu obtížný na pochopení, zde je pár příkladů:

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

Public Sub New()
Console.WriteLine("A()")
End Sub

End Class

Public Class B
Inherits A

Public Sub New()
Console.WriteLine("B()")
End Sub

End Class

Na této ukázce máme třídu A s bezparametrickým konstruktorem a třídu B taktéž s bezparametrickým konstruktorem. Vytvoříme-li instanci třídy B, nejprve se zavolá konstruktor A a následně po něm konstruktor B. Pokud bychom do třídy A konstruktor bez parametrů explicitně nenapsali, kompilátor by ho vygeneroval za nás a fungovalo by to stejně, akorát by nám to nevypsalo informaci na konzoli.

Jen pro ilustraci, pokud máme konstruktor s parametrem, automaticky se bezparametrický konstruktor nevolá:

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

Public Sub New()
Console.WriteLine("A()")
End Sub

Public Sub New(ByVal i As Integer)
Console.WriteLine("A(i)")
End Sub

End Class

Pokud zavoláme konstruktor A s parametrem, vypíše se na konzoli jenom A(i), výchozí konstruktor se nevolá. Pokud chceme, aby se zavolaly oba, musíme jejich použití vynutit explicitně (v C# pomocí dvojtečky a this(), ve VB.NET pomocí volání MyClass.New()).

Z těchto dvou ukázek bychom měli tedy vidět, že pokud nepoužíváme explicitní volání konstruktorů aktuální třídy, volá se v dané třídě vždy a pouze jen jeden z konstruktorů. Pokud neuvedeme žádný konstruktor, kompilátor nám vygeneruje výchozí bezparametrický konstruktor. Pokud voláme konstruktor třídy, která z něčeho dědí, zavolá se nejdřív konstruktor přímého předka (pokud ten z něčeho dědí, tak se před tím opět zavolá konstruktor jeho přímého předka, tedy vlastně prapředka vytvářené třídy atd.).

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

Public Sub New(ByVal i As Integer)
Console.WriteLine("A(i)")
End Sub

End Class

Public Class B
Inherits A

Public Sub New()
MyBase.New(15)
Console.WriteLine("B()")
End Sub

End Class

Pokud nyní do třídy A bezparametrický konstruktor neuvedeme, ale uvedeme tam jiný, který má parametr, kompilátor nám vyhlásí chybu, pokud v konstruktorech B nezavoláme explicitně nějaký konstruktor předka. Je to logické, protože instance A se nedá vytvořit bez zavolání konstruktoru. Jelikož ale všechny konstruktory, které A má, mají parametry, musíme je nějak zadat.

Ve VB.NET se to dělá voláním MyBase.New(parametry), v C# se místo this(parametry) napíše base(parametry).

Kód v jazyce C#
     public class B : A
{
public B() : base(15)
{
Console.WriteLine("B()");
}
}

Volání virtuálních metod z konstruktoru

Pokud jste někdy programovali v C++ a používali dědičnost, najdete zde jisté rozdíly. C++ například podporuje vícenásobnou dědičnost, která v .NETu není (místo ní máme rozhraní, ke kterým se brzy dostaneme).

Co je ovšem zajímavější – volání virtuálních metod z konstruktoru. Mějme třídu B dědící z třídy A, přičemž A i B mají jistou virtuální metodu, v B je přepsaná, aby dělala něco jiného než v A. Pokud zavoláme konstruktor B, zavolá se před tím pochopitelně konstruktor A. Pokud v konstruktoru A voláme naši virtuální metodu, v C++ se zavolá metoda z třídy A. V době, kdy voláme konstruktor A má aktuální instance (this) datový typ A.

V C# a ve VB.NET se při takovémto volání konstruktoru A obsahující volání virtuální metody zavolá virtuální metoda ze třídy B. V případě, že vytváříme instanci třídy B, je i při volání konstruktoru A datový typ this roven B.

Opět se jedná o situaci, na kterou nenarazíte příliš často, ale je dobré o tom vědět, až se budete divit, proč si to zase dělá, co chce, a nedělá to to, co od toho čekáte vy.

Abstraktní metody a třídy

Víme už, jak fungují virtuální metody – při jejích zavolání se podle datového typu objektu (ne proměnné!) a dle jeho tabulky virtuálních metod zjistí, která metoda konkrétně má být zavolána.

Někdy se nám může hodit vyrobit třídu, která je obecným předkem několika dalších, ale zároveň nechceme, aby od této třídy samotné někdo vytvářel instance. Typickým příkladem může být tzv. ISA hierarchie (ISA je zkratka z anglického “is a”, např. “cheetah is a feline” – gepard je kočkovitá šelma).

Máme-li tedy třídy Cheetah, Lion a Garfield dědící z třídy Feline, můžeme třídě Feline označit jisté metody jako abstraktní (protože chceme vynutit, aby si každý potomek tyto metody nutně naimplementoval sám a nehodí se nám nějaké společné chování). V takovém případě označíme třídu Feline jako abstraktní (to musíme vždy, když obsahuje abstraktní metody) a například metodu GetMaximumSpeed (zjistí maximální rychlost běhu šelmy) označíme jako abstraktní, protože každá šelma běhá úplně jinak rychle, nemá smysl tedy tuto metodu implementovat obecně. Navíc nikdy nebudeme v aplikaci mít instanci nějaké obecné kočkovité šelmy, vždy chceme nějakou konkrétní.

Jak abstraktní třídu a metodu nadeklarovat? Celkem snadno:

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

Public MustOverride Function GetMaximumSpeed() As Single

End Class

Public Class Lion
Inherits Feline

Public Overrides Function GetMaximumSpeed() As Single
Return 55
End Function
End Class

Public Class Cheetah
Inherits Feline

Public Overrides Function GetMaximumSpeed() As Single
Return 105
End Function
End Class

Public Class Garfield
Inherits Feline

Public Overrides Function GetMaximumSpeed() As Single
Return 15
End Function
End Class

Ve VB.NET v deklaraci třídy před slovo Class přidáme klíčové slovo MustInherit (musí zdědit), které značí, že třída je abstraktní – obsahuje alespoň jednu abstraktní metodu a nelze z ní vytvářet instance.

Abstraktní metoda pak do vínku dostane klíčové slovo MustOverride a nemá žádné tělo – uvede se jen hlavička, žádné End Sub nebo End Function nemáme.

U implementace tříd dědících z Feline je pak situace naprosto stejná, jako kdyby metody GetMaximumSpeed ve třídě Feline byla virtuální (tedy Overridable). Visual Studio se nám snaží práci co nejvíc usnadnit, takže jakmile napíšeme Inherits Feline a zmáčkneme Enter, doplní nám do třídy samo deklarace všech abstraktních metod, které tato třída má.

Kód v jazyce C#

     public abstract class Feline
{

public abstract float GetMaximumSpeed();

}

public class Lion : Feline
{

public override float GetMaximumSpeed()
{
return 55;
}
}

public class Cheetah : Feline
{

public override float GetMaximumSpeed()
{
return 105;
}
}

public class Garfield : Feline
{

public override float GetMaximumSpeed()
{
return 15;
}
}

V C# před deklaraci třídy i abstraktní metody přidáme klíčové slovo abstract, v poděděných třídách se metody overridují úplně stejně, jako kdyby byly jen virtuální. Visual Studio nám zde také pomáhá, v případě, že napíšeme : Feline, u slova Feline se objeví tzv. Smart Tag, který nám nabídne dogenerování hlaviček abstraktních metod. Rozbalíme ho buď tak, že se do něj trefíme myší (což je pravda dost obtížné), anebo si zapamatujeme klávesovou zkratku Ctrl+. (preferovaný způsob).

Smart Tag

Složitější hierarchie abstraktních tříd

Potomek abstraktní třídy může být opět třída abstraktní. Ta může některé abstraktní metody předka dodefinovat, ale alespoň jednu musí nechat tak, jak je (aby mohla být abstraktní), a donutit tak své potomky, aby je implementovali. Může navíc vytvořit nové abstraktní metody, které její předek nemá.

Pokud chcete dělat abstraktní třídu B dědící z abstraktní třídy A a některé metody z A chcete nechat stále jako abstraktní, tak je dovnitř třídy B jednoduše neuvedete. B samozřejmě musíte označit jako abstraktní.

Podotýkám, že každá abstraktní metoda je automaticky virtuální, je to jen jakési zpřísnění pravidel. Vnitřně se to chová ale naprosto stejně, používá se tabulka virtuálních metod a která metoda se bude volat je určováno za běhu pomocí datového typu konkrétního objektu, ne podle typu proměnné.

Statické třídy

Z minulých dílů byste měli být schopni objasnit rozdíl mezi statickou a instanční metodou a dobře této problematice rozumět. V .NETu můžeme vytvářet i statické třídy, což je třída, od ní podobně jako od abstraktní třídy nemůžeme vyrobit instanci. Statická třída se ale vyznačuje hlavně tím, že obsahuje pouze statické členy – tedy statické metody, statické vnitřní proměnné, statické vlastnosti atd.

V C# se deklaruje umístěním slova static před slovo class. Ve VB.NET se místo statické třídy používá z historických důvodů modul, což vypadá takto:

Kód v jazyce Visual Basic .NET

 Public Module StatickaTrida

Public Sub Metoda()

End Sub

End Module

V C# vypadá statická třída taktoKód v jazyce C#:

     public static class StatickaTrida
{
public static void Metoda()
{
}
}

Ve VB.NET v modulu nepíšeme před metody klíčové slovo Shared, všechny metody jsou ale přesto statické (jsou v modulu). To je trochu matoucí, člověk musí sledovat, jestli je metoda v modulu nebo v třídě. Já osobně doporučuji moduly vůbec nepoužívat a pokud už potřebujete statickou třídu, udělejte normální třídu se statickými metodami. Tak holt nebude statická, ale fungovat to bude naprosto stejně.

Ve VB.NET jsou navíc položky definované v modulu globální. To znamená, že kdekoliv v kódu, který importuje namespace, v němž modul je, můžeme použít prosté volání Metoda(). Pokud by nebylo jasné, z kterého modulu metoda je (např. měli bychom dvě stejně se jmenující), musíme uvést StatickaTrida.Metoda().

V C# je nutné klíčové slovo static napsat před třídu i před metodu (před třídou být nemusí, ale to by pak nebyla třída statická). Volat statickou metodu musíme vždy i včetně názvu třídy, tzn. StatickaTrida.Metoda().

Statické konstruktory

Každá třída může mít navíc statický konstruktor, který se zavolá jen jednou, a to před prvním použitím třídy. Před deklaraci konstruktoru stačí ve VB.NET napsat Shared, v C# pak static. Jen pro připomenutí – všechny statické metody (konstruktor je taky metoda) mohou manipulovat jen se statickými členy třídy, zatímco instanční mohou manipulovat jak s instančními, tak i se statickými.

Viditelnost konstruktorů

Konstruktorům můžeme deklarovat i viditelnost, kterážto definuje, odkud se mohou konstruktory volat. Hodí se třeba udělat private konstruktor, pokud chceme udělat do třídy statickou metodu, která jediná bude smět vytvářet instance třídy.

Uděláme do třídy tedy privátní konstruktor a veřejnou statickou metodu, která je ve stejné třídě a tím pádem může konstruktor zavolat (je privátní). Tato metoda vytvoří proměnnou daného typu, zavolá konstruktor a tuto proměnnou vrátí. Tento princip využívá např. návrhový vzor Factory.

Viditelnost se chová stejně jako u metod, takže pokud uděláte privátní konstruktor, není možné ho zavolat z poděděné třídy (z pochopitelných důvodů).

Zapečetění

Třídy a virtuální metody můžeme nadeklarovat jako zapečetěné (sealed), anebo jak já s oblibou říkám – lachtaní (seal je totiž kromě pečeti také lachtan). U třídy to znamená, že ji není možné podědit, u metody to znamená, že ji není možné overridovat. V C# stačí do deklarace přidat slovo sealed, ve VB.NET přidáte do deklarace třídy slovo NotInheritable a do deklarace metody slovo NotOverridable.

Parciální třídy a konvence pojmenování souborů

Poslední featura, kterou si v tomto díle ukážeme, je parciální třída. V některých jazycích (např. v Javě) je pravidlo, které říká, že každá třída musí být v samostatném souboru, adresářová struktura musí odpovídat struktuře balíčků (jistá analogie namespaces v .NETu) a název souboru musí být stejný jako název třídy (řeší se i velikost písmen).

V .NETu nic podobného neplatí – adresáře nemusí respektovat jmenné prostory, názvy souborů nemají žádnou spojitost s názvem třídy, v jednom souboru může být tříd kolik chce a díky parciálním třídám můžeme naopak jednu třídu rozdělit do více souborů. Je dobrým programátorským zvykem dodržovat konvence, dávat každou třídu do samostatného stejně pojmenovaného souboru a sjednotit strukturu namespaces a adresářů. Nicméně fakt, že toto pravidlo není vynuceno, je v noha případech výhoda, protože v případě, že potřebujeme udělat výjimku a má to své opodstatnění, můžeme.

Parciální třídy, které umožňují tělo třídy rozdělit do více souborů, vznikly z jednoho prostého důvodu – Visual Studio obsahuje pro editaci mnoha typů souborů tzv. designery – vizuální návrháře. Tam můžeme mnoho věcí naklikat (zatímco tučňáci se přiblble hihňají), je to často rychlejší a pohodlnější než psát stovky řádků opakujícího se šablonovitého kódu. Návrhář vygeneruje tento kód za nás, ovšem někdy není vhodné, když nám kus našeho souboru s kódem dogenerovává nějaký nástroj a přistupuje do něj současně s námi. Navíc pokud mu nějak změníme strukturu, se kterou on počítá, může nám celý soubor rozbít.

Díky parciálním třídám je možné celou třídu rozdělit do dvou souborů – jeden kompletně generovaný designerem, do nějž nemáme sahat, protože naše změny mohou být kdykoliv přepsány, a druhý, který je náš.

Hodí se to ale i v jiných situacích – na jednom projektu ve více lidech jsme byli nuceni tři v jeden okamžik pracovat na stejné třídě (každý implementoval její jinou část). Velmi vhodným se ukázalo tuto třídu rozdělit do více souborů a každý z nás měl svůj soubor, díky čemuž se radikálně snížil počet konfliktů ve správě verzí. Zkrátka jsme si nelezli do zelí.

Jak parciální třídu deklarovat? Do dvou nebo více souborů dejte deklaraci té samé třídy, jen před ní připište v C# slovo partial, ve VB.NET totéž slovo Partial, jen s velkým písmenem na začátku (i když VS si to zvětší stejně samo).

Kompilátor všechny deklarace ze všech souborů jedné konkrétní parciální třídy sloučí do jedné a udělá z toho jednu velkou třídu.

Závěrem

V příštím díle se podíváme na již delší dobu slibovaná rozhraní a pak postupně začneme probírat API, tedy knihovní třídy a funkce. Po přečtení tohoto dílu byste měli mít téměř kompletní znalosti dědičnosti a práce s třídami v .NET Frameworku. Je pár drobností, které jsem v článku nezmiňoval, a to proto, že mi nepřišly podstatné. Spíš než znalost všech detailů jazyka považuji za důležité rozumět základním principům, které jsem se snažil vysvětlit. Jakékoliv konstruktivní připomínky v komentářích vítám; pokud máte něco, co v seriálu dosud nezaznělo, sem s tím.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

7. Rozhraní 19.07.2010
6. Dědičnost - dokončení 01.01.2010
5. Dědičnost 09.09.2009
4. Třídy 09.06.2009
3. Datové typy 05.05.2009
2. Základní elementy VB.NET a C# 18.04.2009
1. Úvod do .NET Frameworku 03.04.2009

 

 

 

Nový příspěvek

 

                       
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