NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Lekce 10 - Vlastnosti

V předešlém cvičení, Řešené úlohy k 9. lekci OOP v C# .NET, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V dnešním C# .NET 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álné praxi by vrátila pravděpodobně jen jméno studenta. Nějakého studenta si pomocí konstruktoru vytvořme:

            Student s = new Student("Pavel Hora", true, 20);
            Console.WriteLine(s);
    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);
        }

    }

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();
    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);
        }

    }

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, také nám dojde, že u nich není nejmenší důvod k tomu, abychom je umožňovali modifikovat. Student si za normálních okolností asi jen stěží změní pohlaví nebo jméno. Zároveň by však bylo vhodné atributy vystavit ke čtení, nemůžeme je tedy pouze nastavit jako private. V dřívějších lekcích C# kurzu jsme k tomuto účelu používali metody, které sloužily ke čtení privátních atributů. Pojmenovávali jsme je jako VratVek() a podobně. Ke čtení vybraných atributů tedy vytvoříme také metody a atributy označíme jako privátní, aby se nedaly modifikovat zvenčí. Třída by nově vypadala např. takto (vynechali jsme 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, jež 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 navracení hodnoty se říká gettery a metodám pro zápis settery. Pro editaci ostatních atributů bychom vytvořili jednu metodu EditujStudenta, která by byla podobná konstruktoru. Jméno, věk a podobně by se tedy měnily pomocí této metody. V ní bychom mohli např. kontrolovat, zda hodnoty dávají smysl, a 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# je naštěstí 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 dvě 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 tedy půjde normálně číst i modifikovat:

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

Jediný vnější rozdíl oproti atributu spočívá 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 nebylo možné měnit mimo třídu, označíme setter jako privátní:

public string Jmeno { get; private set; }

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

Chceme-li, aby se v getteru či setteru dělo více než jen načtení/zápis hodnoty, můžeme si je 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;
    }
}

Nejprve je nutné si vytvořit privátní atribut vek s malým písmenem, ve kterém bude hodnota ve skutečnosti uložena. V getteru a setteru poté pracujeme s tímto atributem. Pokud v get{} nebo set{} použijete 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, který jsme si uvedli výše. U drtivé většiny vlastností totiž v metodách nepotřebujeme žádnou logiku.

Občas se můžeme setkat s pojmenováním atributů vlastností s podtržítkem na začátku (tedy např. _vek). Cílem je odlišit tyto atributy od ostatních privátních atributů ve třídě, je to však neobvyklé oproti tomu, jak se atributy v C# běžně pojmenovávají.

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.

Nyní si upravíme 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 mnoho lepší, že? Vlastnosti budeme odteď používat stále, umožňují nám totiž dokonale zapouzdřit objekty. V .NET jsou všechny veřejné atributy tříd vlastnosti (např. nám známá vlastnost Length na řetězci string). Platí pravidlo, že co jde ven, je vlastnost, a co se používá jen uvnitř, je privátní atribut. Veřejný atribut se de facto 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 nyní z konstruktoru 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);
    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);
        }

    }

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.

Automatické read-only vlastnosti

Read-only vlastnosti můžeme kromě private set deklarovat také tak, že setter úplně vynecháme. Taková vlastnost musí mít přiřazenou hodnotu přímo (nebo to můžeme udělat v konstruktoru třídy) a není možné ji zvenku změnit:

public int Plnoletost { get; } = 18;

init settery

Podobně máme možnost setter nastavit jako init. Značíme tím, že vlastnost může být modifikována pouze v konstruktoru nebo při inicializaci instance pomocí {}. Jakmile je vlastnosti jedním z těchto způsobů přiřazena hodnota, stává se opět read-only. Nelze ji mimo konstruktor měnit ani ve třídě, ve které se nachází:

public int Id { get; init; }

Rozdíl oproti read-only vlastnosti spočívá v tom, že při tvorbě instance můžeme vlastnost inicializovat takto:

Student student = new Student
{
    Id = 1,
    Jmeno = "Jan Novák"
};

Lambda výrazy

Zápis jednoduchých getterů a setterů můžeme ještě zkrátit pomocí tzv. lambda výrazů =>, které obecně zkracují zápisy metod vynecháním slova return a závorek { }. K lambdám se sice v kurzu podrobně teprve dostaneme, ale toto jednoduché použití si můžeme představit již nyní, abychom měli kapitolu vlastností kompletní.

Libovolnou vlastnost bychom za předpokladu, že getter i setter obsahují pouze jeden řádek, napsali takto:

private int vlastnost;

public int Vlastnost
{
    get => vlastnost; // getter může obsahovat libovolnou operaci na jeden řádek
    private set => vlastnost = value; // setter může obsahovat libovolnou operaci na jeden řádek
}

Konkrétně bychom lambdu v příkladu s naším člověkem mohli využít pro kontrolu plnoletosti a namísto do setteru Vek ji vložit do getteru Plnolety, čímž by tyto vlastnosti vypadaly následovně:

public int Vek { get; set; }
public bool Plnolety => Vek >= 18;

O lambda výrazech se podrobněji ještě dozvíme dále v OOP kurzu.

V následujícím kvízu, Kvíz - Dědičnost, statika, vlastnosti v C# .NET OOP, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Řešené úlohy k 9. lekci OOP v C# .NET
Všechny články v sekci
Objektově orientované programování v C# .NET
Přeskočit článek
(nedoporučujeme)
Kvíz - Dědičnost, statika, vlastnosti v C# .NET OOP
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
475 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity