Loading...
Loading...
Loading...
Loading...
Je hebt het keyword static
al een paar keer zien staan voor methoden het vorige semester. En dit semester werd er dan weer nadrukkelijk verteld géén static
voor methoden te plaatsen. Wat is het nu?
Een static
onderdeel van een klasse hoort bij de klasse zelf en niet bij specifieke instanties van die klasse. Je hoeft dus geen instanties aan te maken om gebruik te maken van dit onderdeel.
static
kan op 2 manieren gebruikt worden:
Bij variabelen om een gedeelde variabele aan te maken, over de objecten van die klasse heen.
Bij methoden om zogenaamde methoden-bibliotheken of hulpmethoden aan te maken.
Zonder het keyword heeft ieder object z'n eigen variabelen en aanpassingen binnen het object aan die variabelen heeft geen invloed op andere objecten. We tonen eerst de werking zoals we gewend zijn en vervolgens hoe static
werkt.
Gegeven volgende klasse:
Als we dit doen:
Dan zien we volgende uitvoer:
Ieder object houdt de stand van z'n eigen variabelen bij. Ze kunne mekaars interne (zowel publieke als private) staat niet veranderen.
We maken de variabele private int leeftijd
static als volgt: private static int leeftijd=1;
.
We krijgen dan:
We hebben nu gezegd dat ALLE objecten de variabele leeftijd delen. Er wordt van deze variabele dus maar een "instantie" in het geheugen gemaakt.
Voeren we nu terug volgende code uit:
Dan wordt de uitvoer:
Static laat je dus toe om informatie over de objecten heen te delen. Gebruik static niet te pas en te onpas: vaak druist het in tegen de concepten van OO en wordt het vooral misbruikt Ga je dit vaak nodig hebben? Niet zo vaak. Het volgende concept wel.
Heb je er al bij stil gestaan waarom je dit kan doen:
Zonder dat we objecten moeten aanmaken in de trend van:
De reden dat je de math-bibliotheken kan aanroepen rechtsreeks op de klasse en niet op objecten van die klasse is omdat de methoden in die klasse als static
gedefineerd staan.
Stel dat we enkele veelgebruikte methoden willen groeperen en deze gebruiken zonder telkens een object te moeten aanmaken dan doen we dit als volgt:
We kunnen deze methoden nu als volgt aanroepen:
Mooi toch.
In het volgende voorbeeld gebruiken we een static
variabele om bij te houden hoeveel objecten (via de constructor) er van de klasse reeds zijn aangemaakt:
Merk op dat we de methoden VerminderFiets
enkel via de klasse kunnen aanroepen:
private static
?Merk op dat, in bovenstaand voorbeeld, aantalFietsen
private
is en dat er geen publieke property aanwezig is, terwijl een constructor wel bij een instantie hoort. Herinner je ook dat private
onderdelen van een klasse alleen toegankelijk zijn vanuit code binnenin die klasse. De code voor objecten van een bepaalde klasse staat ook binnenin die klasse, dus private static
onderdelen van een klasse zijn toegankelijk voor de objecten van die klasse!
Van zodra je een methode hebt die static
is dan zal deze methode enkel andere `static
methoden en variabelen kunnen aanspreken. Dat is logisch: een static methode heeft geen toegang tot de gewone niet-statische variabelen van een individueel object, want welk object zou hij dan moeten aanpassen?
Volgende code zal dus een error geven:
De error die verschijnt An object reference is required for the non-static field, method, or property 'Program.Fiets.gewicht' zal bij de lijn gewicht--
staan.
Een eenvoudige regel is te onthouden dat van zodra je in een static omgeving (meestal een methode) bent je niet meer naar de niet-static delen van je code zal geraken.
Dit verklaart ook waarom je bij console applicaties in Program.cs steeds alle methoden static
moet maken. Een console-applicatie is als volgt beschreven wanneer je het aanmaakt:
Zoals je ziet is de Main
methode als static
gedefinieerd. Willen we dus vanuit deze methode andere methoden aanroepen dan moeten deze als static
aangeduid zijn.
Uiteraard kan je wel niet-static zaken gebruiken en daarom kan je dus gewone objecten etc. in je static methoden aanmaken.
gebruik van expliciete constructoren
Zorg ervoor dat elke Pokémon bij constructie zijn eigenschappen krijgt.
Voorzie je klasse Pokémon (uit hoofdstuk 9) van een constructor zonder parameters. Dit om bestaande code intact te houden.
Voorzie je klasse Pokémon (uit hoofdstuk 9) van een constructor met vier parameters, één per property. Pas je methode MakePokemon
van hoofdstuk 9 aan zodat gebruik wordt gemaakt van deze constructor in plaats van de constructor zonder argumenten.
(Hier is geen verschil met hoe dit er in hoofdstuk 9 uitzag.)
gebruik van expliciete constructoren
herbruik van bestaande constructor
Wanneer we een willekeurige Pokémon aanmaken, start deze normaal met de helft van zijn maximale hit points. We willen onszelf het werk besparen van dit elke keer uit te typen, dus we voorzien een extra constructor.
Maak een nieuwe constructor met drie argumenten in plaats van vier. Het argument voor HP valt weg. Deze nieuwe constructor maakt eerst gebruik van de bestaande constructor met vier argumenten. Daarna past hij in zijn body de hoeveelheid HP aan naar de helft van het maximum. Bij het gebruik van de meer algemene constructor maakt het niet uit welke waarde je meegeeft voor de huidige hit points. Test je constructor uit met een (statische) demomethode ConstructPokemonChained()
. Deze maakt met deze nieuwe constructor een nieuwe Pokémon (naar keuze) aan met een maximale hoeveelheid HP naar keuze en toont de uitkomst.
verschil tussen klasse en object
We willen bijhouden hoe vaak elk element al gebruikt is voor een aanval. Voeg code toe die bijhoudt hoe vaak een Pokémon van type Grass
al heeft aangevallen, hoe vaak een Pokémon van type Fire
al heeft aangevallen, etc. Voeg ook een statische methode, DemonstrateCounter()
aan die vijf willekeurige Pokémon aanmaakt en elke Pokémon tussen de 5 en de 10 keer laat aanvallen.
Bij een aanval van een Pokémon met een bepaald type, gaat er een teller omhoog. Er is één teller per PokéType
.
(Merk op: er zijn twee Bulbasaurs in het spel, maar alle aanvallen van graspokémon zijn samen geteld.)
goed gebruik van Random
static
constructoren
We schrijven een digitale tombola. Iedere keer een lotje wordt aangemaakt, wordt er een willekeurig getal aan toegekend.
Maak een klasse, Ticket
. Deze is voorzien van één autoproperty: Prize
. Dit is een byte
. Bij aanmaak van een Ticket
wordt deze property ingesteld op een waarde tussen 1 en 100. Schrijf je code zodat dezelfde Random
gebruikt wordt voor alle tickets. Je kan dus geen Random
aanmaken iedere keer je een Ticket
aanmaakt! Maak ook een methode Raffle
(d.w.z. "tombola") om te demonstreren dat dit werkt. Deze methode maakt een rij met 10 lotjes aan en print de waarde van elk lotje in de rij. Het is niet erg dat twee lotjes dezelfde waarde kunnen krijgen.
Schrijf een statische klasse CSVDemo
met een statische methode Run()
. Deze downloadt automatisch dit bestand en print de info in de eerste drie kolommen op het scherm. Tussen elke kolom verschijnt een tab.
We gebruiken het einde van klassen en objecten om iets dieper in de String
klasse te duiken en aan te tonen dat er tal van nuttige zaken bestaan om met strings te werken.
Door een @
(verbatim character) voor een string te plaatsen zeggen we concreet: "de hele string die nu volgt moet je beschouwen zoals hij er staat. Je mag alle escape karakters negeren.
Dit wordt vaak gebruikt om een filepath iets leesbaarder te maken.
Zonder verbatim: string path= "c:\\Temp\\myfile.txt";
Met verbatim: string path= @"c:\Temp\myfile.txt";
Aan het einde van dit hoofdstuk willen we een csv-bestand (comma separated value) splitsen. De Split
methode laat toe een string te splitsen op een bepaald teken. Het resultaat is steeds een array van strings.
Uiteraard kan je dit dus gebruiken om op eender welk char
te splitsen en dus nit enkel een ','
(komma).
Via Join
kunnen we array van string terug samenvoegen. Het resultaat is een nieuwe string.
Volgende voorbeeld zal de eerder array van het vorige voorbeeld opnieuw samenvoegen maar nu met telkens een ;
tussen iedere string:
Voorgaande methoden zijn static
en moet je dus via de klasse String
doen en niet via de objecten zelf.
Volgende methoden/properties kan je rechtstreeks op de string-objecten aanroepen (i.e. niet static
methoden)
int Length
: geeft totaal aantal karakters in de string
IndexOf(string para)
: geeft int
terug die de index bevat waar de string die je als parameter meegaf begint
Trim()
: verwijdersd alle onnodige spaties vooraan en achteraan de string en geeft de opgekuiste string terug
ToUpper()
: zal de meegegeven string naar ALLCAPS omzetten en geeft de nieuwe string als resultaat terug
ToLower()
: idem maar dan naar kleine letters
Replace(string old, string news)
: zal in de string alle substring die gelijk zijn aan old
vervangen door de meegegeven news
string
De correcte manier om strings te vergelijken is met de Compare(string s1, string s2)
methode. Deze zal een int
terug geven:
-1 : de string s1
komt voor de strings2
indien je ze lexicografisch zou sorteren (dit is ongeveer hetzelfde als alfabetisch, maar maakt bijvoorbeeld een onderscheid tussen kleine letters en hoofdletters).
0: beide strings zijn identiek
1: de string s2
komt voor s1
De System.IO
namespace bevat tal van nuttige methoden en klassen om met bestanden te werken. Om gebruik hiervan te maken plaats je bovenaan je file:
Via System.File.ReadAllLines()
kunnen we een tekstbestand uitlezen. De methode geeft een array van string terug. Per lijn die eindigt met een newline (\r\n
) zal een nieuwe string aan de array toegevoegd worden.
Dankzij ReadAllLines
en Split
hebben we nu alle bouwstenen om eenvoudig een csv-bestand te verwerken.
Stel je voor dat een bestand soccer.csv
volgende tekst bevat:
Volgende code zal dit bestand uitlezen en de individuele data op het scherm tonen:
Vaak zal je een online bestand willen verwerken. De WebClient
klasse heeft tal van manieren om met online bronnen te werken. Deze klasse bevindt zich in de System.Net
namespace en vereist dus dat je bovenaan je code volgende lijn toevoegt:
Volgende code toont hoe we een bestand van een specifieke locatie kunnen downloaden:
Dit bestand is 1 platte tekst. Willen we deze vervolgens verwerken dan moeten we deze splitsen in lijnen:
We hebben nu een for
nodig die lijn per lijn zal splitsen:
In dit voorbeeld gaan we er vanuit dat de eerste lijn in het bestand een "header" bevat, die we dus moeten overslaan. Daarom starten we de loop vanaf lijn 1.
Je kan tekst uit een bestand lezen, maar uiteraard kan je ook naar een bestand wegschrijven. De 2 eenvoudigste manieren zijn:
File.WriteAllText
: deze gebruik je als je 1 enkele string wilt wegschrijven
File.WriteAllLines
: deze is de omgekeerde van ReadAllLines()
en zal een array van strings wegschrijven.
Een voorbeeld:
Opgelet met het schrijven naar bestanden: dit zal onherroepelijk het target bestand overschrijven. .Gebruik if(File.Exists(pathtofile))
om te controleren of een bestand bestaat of niet. Eventueel kan je dan aan de gebruiker bevestiging vragen of je deze effectief wilt overschrijven.
Wil je CSV-bestand maken dan zal je eerst met String.Join
een komma-separated lijst maken, bijvoorbeeld:
Objecten die je aanmaakt komen niet zomaar tot leven. Nieuwe objecten maken we aan met behulp van de new
operator zoals we al gezien hebben:
De new
operator doet 2 dingen:
Het maakt een object aan in het geheugen
Het roept de operator van het object aan voor eventuele extra initialisatie
Via de constructor van een klasse kunnen we extra code meegeven die moet uitgevoerd worden telkens een nieuw object van dit type wordt aangemaakt.
De constructor is een unieke methode die wordt aangeroepen bij het aanmaken van een object, daarom dat we ronde haakjes zetten bij new Student()
.
Als programmeur van eigen klassen zijn er 3 opties voor je:
Je gebruikt geen constructors: het leven gaat voort zoals het is. Je kunt objecten aanmaken zoals eerder getoond.
Je hebt enkel een default constructor nodig. Je kan nog steeds objecten met new Student()
aanmaken, maar je gaat zelf beschrijven wat er moet gebeuren bij de default constructor
Je wenst gebruik te maken van een of meerdere overloaded constructoren, hierbij zal je dan extra argumenten kunnen meegeven bij de creatie van een object, bijvoorbeeld: new Student(24, "Jos")
.
Een lege default constructor voor je klasse krijg je standaard wanneer je een nieuwe klasse aanmaakt. Je ziet deze niet en kan deze niet aanpassen. Je kan echter daarom altijd objecten met new myClass()
aanmaken.
Van zodra je echter beslist om zelf een of meerdere constructors te schrijven zal C# zeggen "ok, jij je zin, nu doe je alles zelf". De default constructor die je gratis kreeg zal ook niet meer bestaan en heb je die dus nodig dan zal je die dus zelf moeten schrijven!
De default constructor is een constructor die geen extra parameters aanvaardt. Een constructor bestaat ALTIJD uit volgende vorm:
Dit semester is iedere constructor altijd public
(meer info)
Heeft geen returntype, ook niet void
.
Heeft als naam de naam van de klasse zelf.
Stel dat we een klasse Student
hebben:
We willen telkens een Student-object wordt aangemaakt dat deze een random leeftijd heeft. Via de default constructor kunnen we dat oplossen (je kan namelijk niet schrijven private int age = random.Next(10,20)
)
Eerst schrijven de default constructor, deze ziet er als volgt uit:
Zoals verteld moet de constructor de naam van de klasse hebben, public zijn en geen returntype definiëren.
Vervolgens voegen we de code toe die we nodig hebben:
Dit is in veel programmeertalen slecht gebruik van Random
, maar we hebben nog niet de nodige achtergrond om de juiste werkwijze te tonen. Dat komt binnenkort!
Telkens we nu een object zouden aanmaken met new Student()
zal deze een willekeurige leeftijd hebben.
Als je op een gelijkaardige manier in andere programmeertalen twee of meerdere Studenten snel na mekaar aanmaakt zullen deze dezelfde leeftijd hebben. Dit is omdat ieder object z'n eigen Random
aanmaakt en zoals we weten zal een random generator dezelfde getallen genereren als deze vlak na mekaar (in tijd) zijn aangemaakt. Een oplossing zullen we hier later voor zien. Spoiler, static
is de oplossing hiervoor:
Soms wil je argumenten aan een object meegeven bij creatie. We willen bijvoorbeeld de leeftijd meegeven die het object moet hebben bij het aanmaken. Met andere woorden, stel dat we dit willen schrijven:
Als we dit met voorgaande klasse , die enkel een default constructor heeft, uitvoeren zal de code een fout geven. C# vindt geen constructor die een int als parameter aanvaardt.
Net zoals bij overloading van methoden kunnen we ook constructors overloaden. De code is verrassen gelijkaardig als bij method overloading:
Dat was eenvoudig. Maar denk eraan: je hebt een eigen constructor geschreven en dus heeft C# gezet "ok, je schrijft zelf constructor, trek je plan. Maar de default zal je ook zal moeten schrijven!" Je kan nu enkel je objecten met new Student(25)
aanmaken. Schrijf je new Student()
dan zal je een error krijgen. Wil je die constructor nog hebben, dan zal je die met de hand moeten schrijven, bijvoorbeeld:
Tot zeer recent maakten we onze objecten steeds aan met de default constructor. Pas daarna gaven we eventuele properties de juiste waarde. Dat houdt een risico in: er is een periode waarin onze objecten nog niet "af" zijn. In het slechtste geval vergeten we zelfs om de properties in te stellen en krijgen we objecten die misschien ongeldig zijn.
Constructoren helpen dit probleem te voorkomen. Als we één constructor hebben, bijvoorbeeld Student(string name)
, moeten we die gebruiken. We kunnen dus niet vergeten bijvoorbeeld frankVermeulen.Name = "Frank Vermeulen"
te schrijven, want we worden gedwongen meteen new Student("Frank Vermeulen")
te schrijven.
Samengevat: als er eigenschappen zijn die je meteen bij het aanmaken van een object wil instellen, maak er dan parameters van een constructor voor.
Overloaded constructoren
Wil je meerdere overloaded constructors, dan mag dat ook. Je wilt misschien een constructor die de leeftijd vraag alsook een bool om mee te geven of het om een werkstudent gaat:
Constructor chaining
Als je meerdere overloaded constructoren hebt, hoef je niet in elke constructor alle code voor objectinitialisatie te schrijven. Het sleutelwoordje this
biedt de mogelijkheid eerst een andere constructor aan te roepen en eventueel andere operaties toe te voegen. Dit heet constructor chaining. In bovenstaand voorbeeld kan je ook dit schrijven: