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:
{CSHARP_CONSOLE} Student peter = new Student("Peter Brown", true, 20); Console.WriteLine(peter); {/CSHARP_CONSOLE}
{CSHARP_OOP} 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); } } {/CSHARP_OOP}
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):
{CSHARP_CONSOLE} Student peter = new Student("Peter Brown", true, 20); peter.age = 15; peter.male = false; Console.WriteLine(peter); Console.ReadKey(); {/CSHARP_CONSOLE}
{CSHARP_OOP} 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); } } {/CSHARP_OOP}
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:
{CSHARP_CONSOLE} 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); {/CSHARP_CONSOLE}
{CSHARP_OOP} 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); } } {/CSHARP_OOP}
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#