IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

10. diel - Vlastnosti

V predchádzajúcom cvičení, Riešené úlohy k 9. lekcii OOP v C# .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V dnešnom C# .NET tutoriále sa pozrieme na ďalšie prvky tried, ktoré ešte nepoznáme. Začnime sľúbenými vlastnosťami.

Vlastnosti

Veľmi často sa nám stáva, že chceme mať kontrolu nad zmenami nejakého atribútu objektu zvonku. Budeme chcieť atribút nastaviť ako read-only alebo reagovať na jeho zmeny. Založme si nový projekt (názov Properties) a vytvorme nasledujúcu triedu Student, ktorá bude reprezentovať študenta v nejakom informačnom systéme:

class Student
{
    public string name;
    public bool male;
    public int age;
    public bool fullAged;

    public Student(string name, bool gender, int age)
    {
        this.name = name;
        this.male = gender;
        this.age = age;
        fullAged = true;
        if (age < 18)
            fullAged = false;
    }

    public override string ToString()
    {
        string iAmFullAged = "I am";
        if (!fullAged)
            iAmFullAged = "I am not";
        string gender = "male";
        if (!male)
            gender = "female";
        return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", name, gender, age, iAmFullAged);
    }

}

Trieda je veľmi jednoduchá, študent sa nejako volá, je nejakého pohlavia a má určitý vek. Podľa tohto veku sa nastavuje atribút fullAged pre pohodlnejšie vyhodnocovanie plnoletosti na rôznych miestach systému. Na uloženie pohlavia používame hodnotu bool, či je študent muž. Konštruktor podľa veku určí, či je študent plnoletý. Metóda ToString() je navrhnutá pre potreby tutoriálu tak, aby nám vypísala všetky informácie. V reáli by vrátila pravdepodobne len meno študenta. Pomocou konštruktora si nejakého študenta vytvorme:

            Student peter = new Student("Peter Brown", true, 20);
            Console.WriteLine(peter);
    class Student
    {
        public string name;
        public bool male;
        public int age;
        public bool fullAged;

        public Student(string name, bool gender, int age)
        {
            this.name = name;
            this.male = gender;
            this.age = age;
            fullAged = true;
            if (age < 18)
                fullAged = false;
        }

        public override string ToString()
        {
            string iAmFullAged = "I am";
            if (!fullAged)
                iAmFullAged = "I am not";
            string gender = "male";
            if (!male)
                gender = "female";
            return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", name, gender, age, iAmFullAged);
    }

}

Výstup:

Konzolová aplikácia
I am Peter Brown, male. I am 20 years old and I am of age.

Všetko vyzerá pekne, ale atribúty sú prístupné ako na čítanie, tak na zápis. Objekt teda môžeme rozbiť napríklad takto (hovoríme o nekonzistentnom vnútornom stave):

            Student peter = new Student("Peter Brown", true, 20);
            peter.age = 15;
            peter.male = false;
            Console.WriteLine(peter);
            Console.ReadKey();
    class Student
    {
        public string name;
        public bool male;
        public int age;
        public bool fullAged;

        public Student(string name, bool gender, int age)
        {
            this.name = name;
            this.male = gender;
            this.age = age;
            fullAged = true;
            if (age < 18)
                fullAged = false;
        }

        public override string ToString()
        {
            string iAmFullAged = "I am";
            if (!fullAged)
                iAmFullAged = "I am not";
            string gender = "male";
            if (!male)
                gender = "female";
            return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", name, gender, age, iAmFullAged);
    }

}

Výstup:

Konzolová aplikácia
I am Peter Brown, female. I am 15 years old and I am of age.

Určite musíme ošetriť, aby sa plnoletosť obnovila pri zmene veku. Keď sa zamyslíme nad ostatnými atribútmi, nie je najmenší dôvod, aby sme ich taktiež umožňovali modifikovať. Študent si za normálnych okolností asi len ťažko zmení pohlavie alebo meno. Bolo by však zároveň vhodné ich vystaviť na čítanie, nemôžeme ich teda iba nastaviť ako private. V skorších lekciách C# kurzu sme na tento účel používali metódy, ktoré slúžili na čítanie privátnych atribútov. Ich názov sme volili ako GetAge() a podobne. Na čítanie vybraných atribútov vytvoríme aj metódy a atribúty označíme ako privátne, aby sa nedali modifikovať zvonku. Trieda by novo vyzerala napr. takto (vynechal som konstruktor a ToString()):

class Student
{
    private string name;
    private bool male;
    private int age;
    private bool fullAged;

    ...

    public string GetName()
    {
        return name;
    }

    public bool GetFullAged()
    {
        return fullAged;
    }

    public int GetAge()
    {
        return age;
    }

    public bool Male()
    {
        return male;
    }

    public void SetAge(int value)
    {
        age = value;
        // updating whether student is of age
        fullAged = true;
        if (age < 18)
            fullAged = false;
    }

}

Metódy, čo hodnoty iba vracajú, sú veľmi jednoduché. Nastavenie veku má už nejakú vnútornú logiku, pri jeho zmene musíme totiž prehodnotiť atribút fullAged. Zaistili sme, že sa do premenných nedá zapisovať inak, než my chceme. Máme teda pod kontrolou všetky zmeny atribútov a dokážeme na ne reagovať. Nemôže sa stať, že by nám niekto vnútorný stav nekontrolovane menil a rozbil.

Metódam na vrátenie hodnoty sa hovorí gettery a metódam pre zápis settery. Pre editáciu ostatných atribútov by sme urobili jednu metódu EditStudent(), ktorá by bola podobná konštruktoru. Meno, vek a podobne by sa teda menili pomocou tejto metódy, tam by sme mohli napr. kontrolovať, či hodnoty dávajú zmysel, opäť by sme odchytili všetky pokusy o zmenu na jedinom mieste.

Ručné písanie getterov a setterov je určite veľmi pracné. Nemohol by to urobiť niekto za nás? Áno, C# nám ich vie vygenerovať. Potom už nehovoríme o atribútoch, ale o vlastnostiach. Syntax vlastnosti je veľmi podobná atribútu:

public string Name { get; set; }

Najprv to vyzerá, akoby sme deklarovali atribút. Meno vlastnosti je však veľkým písmenom, jedná sa totiž o metódu (presnejšie dve metódy). V zložených zátvorkách potom špecifikujeme, ktoré metódy si prajeme vygenerovať. Za vlastností nepíšeme bodkočiarku! V ukážke vyššie sa vygeneruje setter aj getter, vlastnosť pôjde teda normálne čítať aj modifikovať:

Console.WriteLine(object.Name); // reading
object.Name = "John Black"; // writing

Jediný rozdiel oproti atribútu je zvonku v tom, že počiatočné písmeno je veľké. C# v skutočnosti vygeneruje privátny atribút a k nemu dve metódy, ktoré podľa kontextu volá (pozná podľa situácie, či čítame, alebo zapisujeme). Keď do vlastnosti nevygenerujeme setter, nepôjde meniť ani zvnútra, ani zvonku. Pokiaľ si prajeme, aby vlastnosť nešla mimo triedu meniť, označíme setter ako privátny:

public string Name { get; private set; }

Tohto budeme hojne využívať a práve takto bude vyzerať väčšina vlastností našich budúcich tried.

Ak si prajeme, aby sa v getteri alebo settere dialo viac, než len načítanie/zápis hodnoty, môžeme si ho definovať ručne. Ukážme si to na našom príklade s plnoletosťou, ktorá sa musí po zmene veku prehodnotiť:

private int age;
public int Age
{
    get
    {
        return age;
    }
    set
    {
        age = value;
        // updating whether student is of age
        fullAged = true;
        if (age < 18)
            fullAged = false;
    }
}

Najprv je nutné si vytvoriť privátny atribút age s malým písmenom, v ktorom bude hodnota v skutočnosti uložená. V getteri a seteri potom pracujeme s týmto atribútom, ak použijete v blokoch get{} alebo set{} vlastnosť Age, program sa zacyklí!. Nie je možné definovať len getter alebo setter, buď sa obaja vygenerujú samy alebo obaja definujeme ručne. Pre prístup k zadanej hodnote je nám v settere k dispozícii kľúčové slovo value. Takto sa v C# do verzie 3.0 museli definovať všetky vlastnosti, až potom Microsoft zaviedol tzv. autoimplementáciu a skrátený zápis, aký sme si uviedli vyššie. Pri drvivej väčšine vlastností totiž v metódach nepotrebujeme žiadnu logiku.

Občas sa môžete stretnúť s pomenovaním atribútov vlastností s podčiarkovníkom na začiatku (teda napr. _age). Cieľom je odlíšiť tieto atribúty od ostatných privátnych atribútov v triede, ale zas je to neobvyklé oproti tomu, ako sa atribúty v C# bežne pomenúvajú.

S Age teraz pracujeme opäť rovnako, ako s atribútom, len s veľkým písmenom. Nenápadné priradenie do veku vnútorne spustí ďalšiu logiku na prehodnotenie atributu fullAged:

object.Age = 15; // the fullAged field will update immediately as well

Rovnako môžeme pochopiteľne implementovať aj getter a napríklad niečo niekam logovať.

Upravíme si našu triedu Student tak, aby používala vlastnosti. Vyzerala by takto:

class Student
{
    public string Name { get; private set; }
    public bool Male { get; private set; }
    public bool FullAged { get; private set; }
    private int age;
    public int Age
    {
        get
        {
            return age;
        }
        set
        {
            age = value;
            // updating whether student is of age
            FullAged = true;
            if (age < 18)
                FullAged = false;
        }
    }

    public Student(string name, bool gender, int age)
    {
        EditStudent(name, gender, age);
    }

    public void EditStudent(string name, bool gender, int age)
    {
        Name = name;
        Male = gender;
        Age = age;
    }

    public override string ToString()
    {
        string iAmFullAged = "I am";
        if (!FullAged)
            iAmFullAged = "I am not";
        string gender = "male";
        if (!Male)
            gender = "female";
        return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", Name, gender, Age, iAmFullAged);
    }
}

To je oveľa lepšie, že? Vlastnosti budeme odteraz používať stále, umožňujú nám totiž objekty dokonale zapuzdriť. V .NET sú všetky verejné atribúty tried vlastnosti (napr. nám známa vlastnosť Length na řeťazci (string). Platí pravidlo, že čo ide von, je vlastnosť, čo sa používa len vo vnútri, je privátny atribút. Verejný atribút sa de facto príliš nepoužíva. Celú triedu aj s ukážkovým programom si samozrejme opäť môžete stiahnuť pod článkom. Kontrolu plnoletosti môžeme z konštruktora teraz vybrať, akonáhle totiž dosadíme do vlastnosti Age, nastaví sa plnoletosť sama. Ešte si opäť vyskúšajme problémový príklad:

            Student peter = new Student("Peter Brown", true, 20);
            peter.Age = 15;
            //peter.Male = false; // This line now causes an error and has to be removed
            Console.WriteLine(peter);
    class Student
    {
        public string Name { get; private set; }
        public bool Male { get; private set; }
        public bool FullAged { get; private set; }
        private int age;
        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                age = value;
                // updating whether student is of age
                FullAged = true;
                if (age < 18)
                FullAged = false;
            }
        }

        public Student(string name, bool gender, int age)
        {
            EditStudent(name, gender, age);
        }

        public void EditStudent(string name, bool gender, int age)
        {
            Name = name;
            Male = gender;
            Age = age;
        }

        public override string ToString()
        {
            string iAmFullAged = "I am";
            if (!FullAged)
                iAmFullAged = "I am not";
            string gender = "male";
            if (!Male)
                gender = "female";
            return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", Name, gender, Age, iAmFullAged);
        }
    }

A výstup:

Konzolová aplikácia
I am Peter Brown, male. I am 15 years old and I am not of age.

Pokiaľ celú vlastnosť označíme ako private, nemožno potom settery alebo gettery označiť ako public.

Automatické read-only vlastnosti

Read-only vlastnosti môžeme okrem private set deklarovať tak, že setter úplne vynecháme. Taká vlastnosť musí mať priradenú hodnotu priamo (alebo to môžeme urobiť v konštruktore triedy) a nie je možné ju zvonku zmeniť:

public int FullAged { get; } = 18;

init settery

Podobne máme možnosť setter nastaviť ako init. Značíme tým, že vlastnosť môže byť modifikovaná iba v konštruktore alebo pri inicializácii inštancie pomocou {}. Akonáhle je vlastnosti jedným z týchto spôsobov priradená hodnota, stáva sa zas read-only. Nie je možné ju mimo konštruktora meniť ani v triede, v ktorej sa nachádza:

public int Id { get; init; }

Rozdiel oproti read-only vlastnosti je, že vlastnosť môžeme inicializovať pri tvorbe inštancie takto:

Student student = new Student
{
    Id = 1,
    Name = "John Black"
};

Lambda výrazy

Zápis jednoduchých getterov a setterov môžeme ešte skrátiť pomocou tzv. lambda výrazov =>, ktoré všeobecne skracujú zápisy metód vynechaním slova return a zátvoriek { }. K lambdám sa síce v kurze podrobne ešte len dostaneme, ale toto jednoduché použitie si môžeme predstaviť už teraz, aby sme mali kompletnú kapitolu vlastností.

Ľubovoľnú vlastnosť by sme za predpokladu, že getter aj setter obsahuje iba jeden riadok, napísali takto:

private int property;

public int Property
{
    get => property; // getter can contain any single line operation
    private set => property = value; // a setter can contain any operation on a single line
}

Konkrétne by sme lambdu v príklade s naším človekom mohli využiť na kontrolu plnoletosti a miesto do setteru vlastnosti Age ju vložiť do gettera vlastnosti FullAged, čím by tieto vlastnosti vyzerali nasledovne:

public int Age { get; set; }
public bool FullAged => Age >= 18;

O lambda výrazoch sa podrobne ešte dozvieme ďalej v OOP kurze.

V nasledujúcom kvíze, Kvíz - Dedičnosť, statika, vlastnosti v C# .NET OOP, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 4x (54.1 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Predchádzajúci článok
Riešené úlohy k 9. lekcii OOP v C# .NET
Všetky články v sekcii
Objektovo orientované programovanie v C# .NET
Preskočiť článok
(neodporúčame)
Kvíz - Dedičnosť, statika, vlastnosti v C# .NET OOP
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
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 sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity