Full properties
We zagen zonet dat instantievariabelen nooit public
mogen zijn om te voorkomen dat de buitenwereld onze objecten 'vult' met slechte zaken. Het voorbeeld waarbij we vervolgens een methode StartVerjongingskuur
gebruikten om op gecontroleerde manier toch aan de interne staat van objecten te komen is één oplossing, maar een nogal oldschool oplossing.
Deze manier van werken - methoden gebruiken om instantievariabelen aan te passen of uit te lezen - is wat voorbij gestreefd binnen C#. Onze programmeertaal heeft namelijk het concept properties (eigenschappen) in het leven geroepen die toelaten op een eenvoudigere manier aan de interne staat van objecten te geraken.
Properties (eigenschappen) zijn de C# manier om objecten hun interne staat in en uit te lezen. Ze zorgen voor een gecontroleerde toegang tot de interne structuur van je objecten.
Star Wars en de nood aan properties
In het Star Wars universum heb je goede oude "Darth Vader". Hij behoort tot de mysterieuze klasse van de Sith Lords. Deze Lords lopen met een geheim rond: ze hebben een Sithnaam. Deze naam mogen de Lords enkel bekend maken aan andere Sith Lords. Voorts heeft een Sith Lord ook een hoeveelheid energie (The Force) waarmee hij kattekwaad kan uithalen. Deze energie kan nooit onder nul gezet worden.
We kunnen voorgaande als volgt schrijven:
Het is uit den boze dat we eenvoudige instantievariabelen (energie
en name
) public
maken. Zouden we dat wel doen dan kunnen externe objecten deze geheime informatie uitlezen!
We willen echter wel van buiten uit het energie-level van een sithLord kunnen instellen. Maar ook hier hetzelfde probleem: wat als we de energie-level op -1000 instellen? Terwijl energie nooit onder 0 mag gaan.
Properties lossen dit probleem op.
2 soorten properties
Er zijn 2 soorten in C#:
Full properties: deze stijl van properties verplicht ons véél code te schrijven, maar we hebben ook volledige controle over wat er gebeurt.
Auto-properties: deze zijn exact het omgekeerde van full properties. Je moet niet veel code schrijven, maar je hebt ook weinig (eigenlijk géén) controle over wat er gebeurt.
Ik behandel eerst full properties, omdat auto-properties een soort afgeleide van full properties zijn. Bepaalde aspecten van full properties worden bij auto-properties achter de scherm verstopt zodat jij als programmeur er geen last van hebt.
Full properties
Properties herken je aan de get
en set
keywords in een klasse. Een property is een beschrijving van wat er moet gebeuren indien je informatie uit (get
) een object wilt halen of informatie in (set
) een object wilt plaatsen.
In volgende voorbeeld maken we een property, genaamd Energie
aan. Deze doet niets anders dan rechtstreeks toegang tot de instantievariabele energie
te geven:
Dankzij voorgaande code kunnen we nu buiten het object de property Energie
gebruiken als volgt:
Laten we eens inzoomen op de full property code.
Full property: identifier en datatype
De eerste lijn van een full property beschrijft de naam (identifier) en datatype van de property: public int Energie
Een property is altijd public
daar dit de essentie van een property net is "de buitenwereld gecontroleerde toegang tot de interne staat van een object geven".
Vervolgens zeggen we wat voor datatype de property moet zijn en geven we het een naam die moet voldoen aan de identifier regels van weleer. Voor de buitenwereld zal een property zich gedragen als een gewone variabele, met de naam Energie
van het type int
.
Indien je de property gaat gebruiken om een instantievariabele naar buiten beschikbaar te stellen, dan is het een goede gewoonte om dezelfde naam als dat veld te nemen maar nu met een hoofdletter (dus Energie
i.p.v. energie
).
Full property: get gedeelte
Indien je wenst dat de property data naar buiten kan sturen, dan schrijven we de get-code. Binnen de accolades van de get
schrijven we wat er naar buiten moet gestuurd worden.
Dit werkt dus identiek aan een methode met een returntype. Het element dat je met return
teruggeeft in de get code moet uiteraard van hetzelfde type zijn als waarmee je de property hebt gedefinieerd (int
in dit geval).
We kunnen nu van buitenaf toch de waarde van energie
uitlezen via de property en het get-gedeelte, bijvoorbeeld int uitgelezen = palpatine.Energie;
.
We mogen eender wat doen in het get-gedeelte (net zoals bij methoden) zolang er finaal maar iets uitgestuurd wordt m.b.v. return
. Ik zal hier verderop meer over vertellen, want soms is het handig om getters te schrijven die de data transformeren voor ze uitgestuurd wordt.
Full property: set gedeelte
In het set-gedeelte schrijven we de code die we moeten hanteren indien men van buiten een waarde aan de property wenst te geven om zo een instantievariabele aan te passen.
De waarde die we van buiten krijgen (als een parameter zeg maar) zal altijd in een lokale variabele value
worden bewaard binnenin de set-code. Deze zal van het type van de property zijn.
Deze value
parameter is een geserveerd keyword van de set
syntax en kan je niet hernoemen of voor iets anders gebruiken.
Vervolgens kunnen we value
toewijzen aan de interne variabele indien gewenst: energie = value;
. Uiteraard kunnen we die toewijzing dus ook gecontroleerd laten gebeuren, wat ik zo meteen uitleg.
We kunnen vanaf nu van buitenaf waarden toewijzen aan de property en zo energie
toch bereiken: palpatine.Energie = 50;
.
Je bent niet verplicht om een property te maken wiens naam overeen komt met een bestaande instantievariabele (maar dit wordt wel aangeraden).
Dit mag dus ook:
Visual Studio heeft een ingebouwde snippet om snel een full property, inclusief een bijhorende private instantievariabele, te schrijven. Typ "propfull" gevolgd door twee maal op de tab-toets te duwen.
Full property met toegangscontrole
De full property Energie
heeft nog steeds het probleem dat we negatieve waarden kunnen toewijzen (via de set
) die dan vervolgens zal toegewezen worden aan energie
.
Properties hebben echter de mogelijkheid om op te treden als wachters van en naar de interne staat van objecten.
We kunnen in de set
code extra controles inbouwen. Aangezien de variabele value
de waarde krijgt die we extern aan de property toewijzen, kunnen we deze controleren en zo nodig de toewijzing voorkomen. Volgende voorbeeld zal enkel de waarde toewijzen indien deze groter of gelijk aan 0 is:
Volgende lijn zal dus geen effect hebben:
We mogen de code binnen set
en get
zo complex maken als we zelf willen.
Probeer wel steeds de OOP-principes te hanteren wanneer je met properties werkt: in de get
en set
van een property mogen enkel die dingen gebeuren die de verantwoordelijkheid van de property zelf zijn. Je gaat dus bijvoorbeeld niet controleren of een andere property geen illegale waarden krijgt, daar is die andere property voor verantwoordelijk.
Property variaties
We zijn niet verplicht om zowel de get
en de set
code van een property te schrijven. Dit laat ons toe om een aantal variaties te schrijven:
Write-only property: heeft geen
get
.Read-only property: heeft geen
set
.Read-only property met private
set
: het omgekeerde, een privateget
, zal je zelden tegenkomen.Read-only property die data transformeert: om interne data in een andere vorm uit je object te krijgen.
Write-only property
Dit soort properties zijn handig indien je informatie naar een object wenst te sturen dat niet mag of moet uitgelezen kunnen worden. Het meest typische voorbeeld is een property Pincode
van een klasse BankRekening
.
We kunnen dus enkel energie
een waarde geven, maar niet van buiten uitlezen.
Read-only property
Letterlijk het omgekeerde van een write-only property. Deze gebruik je vaak wanneer je informatie uit een object wil kunnen uitlezen uit een instantievariabele dat NIET door de buitenwereld mag aangepast worden.
We kunnen enkel energie
van buiten uitlezen, maar niet aanpassen.
Het readonly
keyword heeft andere doelen en wordt NIET gebruikt in C# om een readonly property te maken.
Read-only property met private set
Soms gebeurt het dat we van enkel voor de buitenwereld de property read-only willen maken. We willen in de klasse zelf nog steeds controleren dat er geen illegale waarden aan private instantievariabelen worden gegeven. Op dat moment definiëren we een read-only property met een private setter:
Van buiten zal enkel code werken die de get
van deze property aanroept, bijvoorbeeld:
Code die de set
van buiten nodig heeft (bv. palpatine.Energie = 65;
) zal een fout geven ongeacht of deze geldig is of niet.
Het is een goede gewoonte om altijd via de properties je interne variabele aan te passen en niet rechtstreeks via de instantievariabele zelf. Dit is zo'n nuttige tip dat we op de volgende pagina de voorman hier ook nog even over aan het woord gaan laten.
Nu even goed opletten: indien we in het object de instantievariabelen willen aanpassen dan is het een goede gewoonte om ook dat via de property te doen (ook al zit je in het object zelf en heb dus eigenlijk de property niet nodig). Zo zorgen we ervoor dat de bestaande controle in de property niet wordt omzeilt. Kijk zelf naar volgende slechte codevoorbeeld:
De nieuw toegevoegde methode ResetLord
willen we gebruiken om de lord z'n energie terug te verlagen. Als we deze methode met een negatieve waarden aanroepen zullen we alnsog energie
op een verkeerde waarde instellen. Nochtans is dit een illegale waarde volgens de set-code van de property.
We moeten dus in de methode ook expliciet via de property gaan om bugs te voorkomen en dus gaan we in ResetLord
schrijven naar de property Energie
én niet rechtstreeks naar de instantievariabele energie
:
Read-only properties die transformeren
Je bent uiteraard niet verplicht om voor iedere instantievariabele een bijhorende property te schrijven. Omgekeerd ook: mogelijk wil je extra properties hebben voor data die je 'on-the-fly' kan genereren dat niet noodzakelijk uit een instantievariabele komt. Stel dat we volgende klasse hebben:
We willen echter ook soms de volledige naam of emailadres krijgen, beide gebaseerd op de inhoud van de instantievariabelen voornaam
en achternaam
. Via een read-only property die transformeert kan dit:
Methode of property?
Een veel gestelde vraag bij beginnende OOP-ontwikkelaars is: "Moet dit in een property of in een methode geplaatst worden?"
De regels zijn niet in steen gebeiteld, maar ruwweg kan je stellen dat:
Betreft het een actie of gedrag: iets dat het object moet doen (tekst tonen, iets berekenen of aanpassen, enz.) dan plaats je het in een methode.
Betreft het een eigenschap van het object, dan gebruik je een property indien het om data gaat die snel verkregen of berekend kan worden. Gaat het om data die zwaardere en/of langere berekeningen vereist dan is een methode nog steeds aangeraden.
Last updated