Přidej si svou IT školu do profilu a najdi spolužáky zde na síti :)

10. díl - Vlastnosti

C# .NET Objektově orientované programování Vlastnosti American English version English version

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Statika, jsme si vysvětlili statiku. V dnešním tutoriálu se podíváme na další prvky tříd, které ještě neznáme. Začněme slíbenými vlastnostmi.

Vlastnosti

Velmi často se nám stává, že chceme mít kontrolu nad změnami nějakého atributu objektu zvenčí. Budeme chtít atribut nastavit jako read-only nebo reagovat na jeho změny. Založme si nový projekt (název Vlastnosti) a vytvořme následující třídu Student, která bude reprezentovat studenta v nějakém informačním systému.

class Student
{
        public string jmeno;
        public bool muz;
        public int vek;
        public bool plnolety;

        public Student(string jmeno, bool pohlavi, int vek)
        {
                this.jmeno = jmeno;
                this.muz = pohlavi;
                this.vek = vek;
                plnolety = true;
                if (vek < 18)
                        plnolety = false;
        }

        public override string ToString()
        {
                string jsemPlnolety = "jsem";
                if (!plnolety)
                        jsemPlnolety = "nejsem";
                string pohlavi = "muž";
                if (!muz)
                        pohlavi = "žena";
                return String.Format("Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.", jmeno, pohlavi, vek, jsemPlnolety);
        }

}

Třída je velmi jednoduchá, student se nějak jmenuje, je nějakého pohlaví a má určitý věk. Podle tohoto věku se nastavuje atribut plnolety pro pohodlnější vyhodnocování plnoletosti na různých místech systému. K uložení pohlaví používáme hodnotu bool, zda je student muž. Konstruktor dle věku určí, zda je student plnoletý. Metoda ToString() je navržena pro potřeby tutoriálu tak, aby nám vypsala všechny informace. V reálu by vrátila pravděpodobně jen jméno studenta. Pomocí konstruktoru si nějakého studenta vytvořme:

Student s = new Student("Pavel Hora", true, 20);
Console.WriteLine(s);

Výstup:

Konzolová aplikace
Jsem Pavel Hora, muž. Je mi 20 let a jsem plnoletý.

Vše vypadá hezky, ale atributy jsou přístupné jak ke čtení, tak k zápisu. Objekt tedy můžeme rozbít například takto (hovoříme o nekonzistentním vnitřním stavu):

Student s = new Student("Pavel Hora", true, 20);
s.vek = 15;
s.muz = false;
Console.WriteLine(s);
Console.ReadKey();

Výstup:

Konzolová aplikace
Jsem Pavel Hora, žena. Je mi 15 let a jsem plnoletý.

Určitě musíme ošetřit, aby se plnoletost obnovila při změně věku. Když se zamyslíme nad ostatními atributy, není nejmenší důvod, abychom je taktéž umožňovali modifikovat. Student si za normálních okolností asi jen stěží změní pohlaví nebo jméno. Bylo by však zároveň vhodné je vystavit ke čtení, nemůžeme je tedy pouze pouze nastavit jako private. V dřívějších dílech seriálu jsme k tomuto účelu používali metody, které sloužily ke čtení privátních atributů. Jejich název jsme volili jako VratVek() a podobně. Ke čtení vybraných atributů vytvoříme také metody a atributy označíme jako privátní, aby se nedaly modifikovat zvenčí. Třída by nově vypadala např. takto (vynechal jsem konstruktor a ToString()):

class Student
{
        private string jmeno;
        private bool muz;
        private int vek;
        private bool plnolety;

        ...

        public string VratJmeno()
        {
                return jmeno;
        }

        public bool VratPlnoletost()
        {
                return plnolety;
        }

        public int VratVek()
        {
                return vek;
        }

        public bool Muz()
        {
                return muz;
        }

        public void NastavVek(int hodnota)
        {
                vek = hodnota;
                // přehodnocení plnoletosti
                plnolety = true;
                if (vek < 18)
                        plnolety = false;
        }


}

Metody, co hodnoty jen vracejí, jsou velmi jednoduché. Nastavení věku má již nějakou vnitřní logiku, při jeho změně musíme totiž přehodnotit atribut plnolety. Zajistili jsme, že se do proměnných nedá zapisovat jinak, než my chceme. Máme tedy pod kontrolou všechny změny atributů a dokážeme na ně reagovat. Nemůže se stát, že by nám někdo vnitřní stav nekontrolovaně měnil a rozbil.

Metodám k navrácení hodnoty se říká gettery a metodám pro zápis settery. Pro editaci ostatních atributů bychom udělali jednu metodu EditujStudenta, která by byla podobná konstruktoru. Jméno, věk a podobně by se tedy měnily pomocí této metody, tam bychom mohli např. kontrolovat, zda hodnoty dávají smysl, opět bychom odchytili všechny pokusy o změnu na jediném místě. Ruční psaní getterů a setterů je jistě velmi pracné. Nemohl by to udělat někdo za nás? Ano, C# nám je umí vygenerovat. Poté již nehovoříme o atributech, ale o vlastnostech.

Syntaxe vlastnosti je velmi podobná atributu:

public string Jmeno { get; set; }

Zprvu to vypadá, jako bychom deklarovali atribut. Jméno vlastnosti je však velkým písmenem, jedná se totiž o metodu (přesněji 2 metody). Ve složených závorkách poté specifikujeme, které metody si přejeme vygenerovat. Za vlastností nepíšeme středník! V ukázce výše se vygeneruje setter i getter, vlastnost půjde tedy normálně číst i modifikovat:

Console.WriteLine(objekt.Jmeno); // číst
objekt.Jmeno = "Jan Malý"; // zapisovat

Jediný rozdíl oproti atributu je zvenčí v tom, že počáteční písmeno je velké. C# ve skutečnosti vygeneruje privátní atribut a k němu dvě metody, které podle kontextu volá (pozná dle situace zda čteme nebo zapisujeme). Když do vlastnosti nevygenerujeme setter, nepůjde měnit ani zevnitř, ani zvenčí. Pokud si přejeme, aby vlastnost nešla mimo třídu měnit, označíme setter jako privátní:

public string Jmeno { get; private set; }

Tohoto budeme hojně využívat a právě takto bude vypadat většina vlastností našich budoucích tříd.

Pokud si přejeme, aby se v getteru nebo setteru dělo více, než jen načtení/zápis hodnoty, můžeme si ho definovat ručně. Ukažme si to na našem příkladu s plnoletostí, která se musí po změně věku přehodnotit:

private int vek;
public int Vek
{
        get
        {
                return vek;
        }
        set
        {
                vek = value;
                // kontrola plnoletosti
                Plnolety = true;
                if (vek < 18)
                        Plnolety = false;
        }
}

Zprvu je nutné si vytvořit privátní atribut vek s malým písmenem, ve které bude hodnota ve skutečnosti uložena. V getteru a setteru poté pracujeme s tímto atributem, pokud použijete v get nebo set Vek, program se zacyklí!. Není možné definovat jen getter nebo setter, buď se oba vygenerují samy nebo oba definujeme ručně. Pro přístup k zadané hodnotě je nám v setteru k dispozici klíčové slovo value. Takto se v C# do verze 3.0 musely definovat všechny vlastnosti, až poté Microsoft zavedl tzv. autoimplementaci a zkrácený zápis, jaký jsme si uvedli výše. u drtivé většiny vlastností totiž v metodách nepotřebujeme žádnou logiku. S Vek nyní pracujeme opět stejně, jako s atributem, jen s velkým písmenem. Nenápadné přiřazení do věku vnitřně spustí další logiku k přehodnocení vlastnosti Plnolety:

objekt.Vek = 15; // nyní se změní i plnoletost

Stejně můžeme pochopitelně implementovat i getter a například něco někam logovat.

Upravíme si naši třídu Student tak, aby používala vlastnosti. Vypadala by takto:

class Student
{
        public string Jmeno { get; private set; }
        public bool Muz { get; private set; }
        public bool Plnolety { get; private set; }
        private int vek;
        public int Vek
        {
                get
                {
                        return vek;
                }
                set
                {
                        vek = value;
                        // kontrola plnoletosti
                        Plnolety = true;
                        if (vek < 18)
                                Plnolety = false;
                }
        }

        public Student(string jmeno, bool pohlavi, int vek)
        {
                EditujStudenta(jmeno, pohlavi, vek);
        }

        public void EditujStudenta(string jmeno, bool pohlavi, int vek)
        {
                Jmeno = jmeno;
                Muz = pohlavi;
                Vek = vek;
        }

        public override string ToString()
        {
                string jsemPlnolety = "jsem";
                if (!Plnolety)
                        jsemPlnolety = "nejsem";
                string pohlavi = "muž";
                if (!Muz)
                        pohlavi = "žena";
                return String.Format("Jsem {0}, {1}. Je mi {2} let a {3} plnoletý.", Jmeno, pohlavi, Vek, jsemPlnolety);
        }

}

To je o hodně lepší, že? Vlastnosti budeme odteď používat stále, umožňují nám totiž objekty dokonale zapouzdřit. V .NET jsou všechny veřejné atributy tříd vlastnosti (např. nám známá vlastnost Length na stringu), platí pravidlo, že co jde ven je vlastnost, co se používá jen uvnitř je privátní atribut. Veřejný atribut se defakto příliš nepoužívá. Celou třídu i s ukázkovým programem si samozřejmě opět můžete stáhnout pod článkem. Kontrolu plnoletosti můžeme z konstruktoru nyní vyjmout, jakmile totiž dosadíme do vlastnosti Vek, nastaví se plnoletost sama. Ještě si opět vyzkoušejme problémový příklad:

Student s = new Student("Pavel Hora", true, 20);
s.Vek = 15;
// s.Muz = false; // tento řádek nyní způsobí chybu a musí být odebrán
Console.WriteLine(s);

A výstup:

Konzolová aplikace
Jsem Pavel Hora, muž. Je mi 15 let a nejsem plnoletý.

Pokud celou vlastnost označíme jako private, nelze poté settery nebo gettery označit jako public.

V příští lekci, Datum a čas v C#, se podíváme jak se v .NET pracuje s datem a časem.


 

Stáhnout

Staženo 1075x (26.85 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
22 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Miniatura
Předchozí článek
Statika
Miniatura
Následující článek
Datum a čas v C#
Aktivity (8)

 

 

Komentáře
Zobrazit starší komentáře (49)

Avatar
gcx11
Redaktor
Avatar
gcx11:26. března 17:47

Ahoj, protože kompilátor neví, že malé b má být private hodnota pro tvoje velké B. Kdybys to malé b změnil třeba na c, tak se to bude chovat stejně. Ve velkém B máš implicitně 0, jelikož to je defaultní hodnota, na kterou se inicializuje int. Správně řešení je dát pryč private b úplně a používat velké B. Kompilátor si za Tebe vytvoří nějakou proměnnou, ve které bude private hodnota B.

class Rectangle
    {
        private double a;;

        public Rectangle(double a, double b)           //Class Rectangle - deklarace třídy s parametry a, b
        {
            this.a = a;
            this.B = b;
        }


        public double Obvod()                          //Metoda pro obvod
        {
            return (2 * a + 2 * B);
        }
        public double Obsah() //                     Metoda pro obsah
        {
            return (a * B);
        }

        public double A { get { return a; } set { a = value; } }    //zmena promennych pomoci vlastnosti - funguje
        public double B { get; set; }                               //zkraceny zapis funkcni
    }
 
Odpovědět 26. března 17:47
Avatar
Odpovídá na gcx11
Bruno Schwarzbach:28. března 18:01

Děkuji za odpověď.

 
Odpovědět 28. března 18:01
Avatar
Trenky
Člen
Avatar
Trenky:2. srpna 13:22

Miesto tohoto:
plnolety = true;
if (vek < 18)
plnolety = false;
by sa mohlo dať použiť toto :)
plnolety = vek>=18;

 
Odpovědět 2. srpna 13:22
Avatar
Petr Klekner
Člen
Avatar
Petr Klekner:7. září 11:31

Ahoj chci se jenom ujistit, taže pokuď zapisuju atributy třídy používám ktomu způsob Camel Case je to tak ? A v případě vlastnosti Pascal case

Editováno 7. září 11:32
 
Odpovědět 7. září 11:31
Avatar
Riči Jak
Člen
Avatar
Riči Jak:20. září 14:00

Ahoj, ještě bych možná doplnil stát, neboť v některých státech je plnoletost v 21 letech, ale to jen tak na okraj, programujeme přeci jen v češtině ;-)

 
Odpovědět 20. září 14:00
Avatar
Vojtěch Giesl:9. října 12:06

Na čem závisí že můžeme settrem změnit věk, ale ne třeba jméno nebo pohlaví? Respektive proč můžeme napsat

s.Vek = 15;

ale už ne třeba

s.Muz = false;
 
Odpovědět 9. října 12:06
Avatar
Odpovídá na Vojtěch Giesl
Michal Štěpánek:9. října 12:22
public bool Muz { get; private set; }

na tom "private"

Odpovědět 9. října 12:22
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Vojtěch Giesl:9. října 12:31

Jasně, díky. To mi uniklo.
Teď, ale vyskakuje jiná otázka, k čemu mi je potom takovej setter, když ním v podstatě nemůžu nic nastavit?

 
Odpovědět 9. října 12:31
Avatar
Odpovídá na Vojtěch Giesl
Michal Štěpánek:9. října 13:08

Je to tam právě proto, aby se ta hodnota nedala měnit od jinud, než právě z "té" třídy...

Odpovědět 9. října 13:08
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Odpovídá na Michal Štěpánek
Vojtěch Giesl:9. října 13:09

Už to chápu, díky za vysvětlení.

 
Odpovědět 9. října 13:09
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 59. Zobrazit vše