Wat leer je in dit hoofdstuk?

Na dit hoofdstuk kan je:

een constructor schrijven om de beginstaat van een object in te stellen

het verschil tussen een default en een overloaded constructor uitleggen

constructors hergebruiken met this()

objecten opzetten met object initializer syntax en required

het keyword static gebruiken voor fields, methoden, properties en klassen

uitleggen waarom een static deel niet bij de niet-static delen kan

Waar staan we?

Een klasse bestond tot nu uit:

instantievariabelen

de toestand

methoden

het gedrag

properties

gecontroleerde toegang

Daar komen nu bij:

constructors

static

object initializer syntax

Constructors

De new operator

Student frank = new Student();

new doet drie dingen:

1. een object aanmaken in de heap

2. de constructor aanroepen voor extra initialisatie

3. een referentie naar dat object teruggeven

Vandaar de ronde haakjes bij new Student(): dat is de constructor-aanroep.

Soorten constructors

Geen zelf geschreven constructor

je krijgt gratis een onzichtbare default constructor

Enkel een default constructor (zonder parameters)

maar zelf ingevuld

Een of meer overloaded constructors

met parameters, bv. new Student("Jos")

Waarschuwing

Net als met aannemers: soms ruimen ze zelf op, soms niet. Schrijf je zelf een constructor, dan ben je de gratis default constructor kwijt.

Een default constructor

internal class Student
{
    public Student()
    {
        UurVanInschrijven = DateTime.Now.Hour;
    }
    public int UurVanInschrijven { get; private set; }
}

Altijd public

geen returntype

naam = klassenaam

( ) geen parameters

Tip

Enkel een auto-property een startwaarde geven? Dan volstaat public int X { get; set; } = 2; zonder constructor.

Overloaded constructors

public Student(string bijnaamIn)
{
    BijNaam = bijnaamIn;
}

Zo kan je new Student("Lord Oakenwood") schrijven.

Waarschuwing

Vanaf nu werkt new Student() niet meer: “Student does not contain a constructor that takes 0 arguments”. Wil je de default constructor terug? Schrijf hem dan zelf.

Dubbele code in constructors

Stel: drie constructors die elk Naam, Adres en Saldo instellen.

constructor 1

Naam Adres Saldo

constructor 2

Naam Adres Saldo

constructor 3

Naam Adres Saldo

Splits je later Adres op in Straat + Stad, dan moet je elke constructor apart aanpassen.

Waarschuwing

Code die je op meerdere plekken herhaalt, is vragen om problemen. Eén bron van waarheid is beter.

this(): constructors hergebruiken

Vermijd dubbele code door één constructor de andere te laten aanroepen:

public Microfoon(string merkIn, bool isUitverkochtIn)
{
    Merk = merkIn;
    IsUitverkocht = isUitverkochtIn;
}

public Microfoon(string merkIn) : this(merkIn, false) { }

public Microfoon() : this("Onbekend", true) { }

De compiler kiest via overload resolution de juiste constructor.

Welke constructors voorzien?

Minstens genoeg om een object in een geldige staat te brengen:

public Breuk(int tellerIn, int noemerIn)
{
    Teller = tellerIn;
    Noemer = noemerIn;   // set weigert noemer 0
}

Tip

Door enkel deze constructor te voorzien, dwing je af dat niemand een Breuk() zonder noemer maakt (en zo een DivideByZeroException riskeert).

Object initializer syntax

Object initializer syntax: de werking

Geef bij de creatie properties een beginwaarde, zonder een aparte constructor:

Meting m = new Meting { Temperatuur = 3.4, IsGeconfirmeerd = true };

Werkt via de default constructor en daarna de properties

Enkel voor properties met een van buitenaf bereikbare set

Opmerking

Intern: eerst de default constructor, dan m.Temperatuur = 3.4; enzovoort.

required properties

Wil je toch verplichten dat een property wordt ingesteld bij object initializer syntax? Gebruik required:

internal class Meting
{
    public double Temperatuur { get; set; }
    public required bool IsGeconfirmeerd { get; set; }
}
Meting m = new Meting { Temperatuur = 0.7 }; // FOUT: IsGeconfirmeerd ontbreekt

Belangrijk

required bestaat sinds C# 11 (.NET 7+).

Static

static: het idee

static geeft aan dat een element bij de klasse zelf hoort, niet bij een individueel object.

Gevolg:

je kan het aanroepen zonder een object aan te maken

de data wordt gedeeld over alle objecten

static kan op fields methoden properties en zelfs de hele klasse

static fields

internal class Mens
{
    private static int teller = 0;
    public Mens() { teller++; }
}

Eén exemplaar, gedeeld door alle objecten: maak je drie Mens-objecten, dan staat teller op 3.

Waarschuwing

Gebruik static met mate: het druist vaak in tegen OOP. Het past bij zaken die bij de klasse horen (een teller), niet bij een individueel object (een geboortejaar).

static methoden en klassen

Daarom kan je Math.Pow(3, 2) zonder een Math-object te maken:

internal static class EpicLibrary
{
    public static void ToonInfo() { Console.WriteLine("Ik ben ik"); }
    public static int TelOp(int a, int b) { return a + b; }
}

EpicLibrary.ToonInfo();
int som = EpicLibrary.TelOp(3, 5);
Ik ben ik

Belangrijk

Een static class mag enkel static members bevatten. De compiler dwingt dat af: je kan er toch nooit een object van maken.

static versus non-static

Een static methode kan enkel andere static leden aanspreken: ze hoort bij geen enkel object.

private int gewicht = 50;
private static void Verminder()
{
    gewicht--; // FOUT
}

Wat mag wel, wat niet.

Tip

Daarom moet in Program.cs alles static zijn: Main is zelf static.

static properties

Alle balletjes delen dezelfde speelveldgrenzen:

static public int Breedte { get; set; }
static public int Hoogte { get; set; }
Balletje.Hoogte = Console.WindowHeight;
Balletje.Breedte = Console.WindowWidth;

Elk balletje gebruikt in Update() gewoon Balletje.Breedte, zonder de grenzen zelf te kennen.

static en Random

Een nieuwe Random bij elke worp is een slecht idee. Met een vaste seed (om te testen) gooit elke worp hetzelfde getal, en zonder seed maak je telkens een object aan dat je meteen weer weggooit.

internal class Dobbelsteen
{
    static Random gen = new Random(); // gedeeld
    public int Werp() { return gen.Next(1, 7); }
}

Tip

Eén gedeelde, static generator lost dat netjes op: de afspraak uit hoofdstuk 4, één generator voor je hele programma.

Uitdaging: Crazy House

Een tekst-avontuur waarin de speler van kamer naar kamer wandelt:

Room

  • deuren naar noord/oost/zuid/west, elk een referentie naar een andere Room
  • een Describe() die de kamer en de uitgangen toont

Player

  • houdt zijn HuidigeKamer bij
  • een MoveTo(richting) om (te proberen) verplaatsen

Tip

Een object dat een ander object als instantievariabele bevat: de kern van grotere OOP-programma’s. Uitbreiding: geef sommige kamers een NPC met een Speak()-tip.

Conclusie

De constructor zet een object meteen in een geldige beginstaat (via new)

Schrijf je zelf een constructor, dan ben je de gratis default constructor kwijt

this() hergebruikt constructors; object initializer syntax en required vullen properties in

static hoort bij de klasse, niet bij een object: gedeelde data en hulpmethoden

Een static deel kan enkel andere static delen aanspreken

Tip

Volgende halte: arrays en collecties van objecten (List, foreach, Dictionary).

Meer weten

Kennisclips

Oefeningen H11 · Quizlet flashcards

Zie verder

Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.

Zie verder: constructor in andere talen

In Python heet de constructor altijd __init__ (eerste parameter self):

class Auto:
    def __init__(self, merk):
        self.merk = merk

In JavaScript heet hij gewoon constructor:

class Auto {
    constructor(merk) { this.merk = merk; }
}

Zie verder: meerdere constructors

C#, Java en C++

laten meerdere constructors toe (overloading)

Python kent dat niet

één __init__, en je lost het op met default-parameterwaarden

class Student:
    def __init__(self, bijnaam, is_werkstudent=False):
        self.bijnaam = bijnaam
        self.is_werkstudent = is_werkstudent

Zowel Student("Jos") als Student("Jos", True) werkt: één constructor die zich als meerdere gedraagt.

Zie verder: static in andere talen

In Java werkt static zo goed als identiek aan C#:

class Fiets {
    static int aantalFietsen = 0;
    static int getAantal() { return aantalFietsen; }
}
// Fiets.getAantal();

In Python is een variabele rechtstreeks in de klasse (zonder self) gedeeld, en @staticmethod maakt een methode die je op de klasse aanroept:

class Fiets:
    aantal_fietsen = 0
    @staticmethod
    def beschrijf(): print("Een fiets")
# Fiets.beschrijf()

In alle gevallen hoort dit bij de klasse, niet bij een los object.

Zie verder: zoek de fout

Onderstaande Java-klasse wil bijhouden hoeveel fietsen er gemaakt zijn, maar de teller blijft hangen op 1. Wat loopt er mis?

class Fiets {
    int aantalFietsen = 0;
    Fiets() {
        aantalFietsen++;
    }
}

Opmerking

aantalFietsen is hier een gewone instantievariabele, dus elk Fiets-object krijgt z’n eigen kopie die in de constructor van 0 naar 1 gaat. Je wilt één teller die gedeeld wordt over alle objecten, dus moet de variabele static zijn: static int aantalFietsen = 0;. Dezelfde redenering geldt in C#.

Samenvattende poster

Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).