• ToString overschrijf je met precies public override string ToString(). Ze geeft tekst terug en toont zelf niets: Console.WriteLine(mijnObject) en $"{mijnObject}" roepen ze voor jou op (uitleg).
  • Overschrijf je Equals, overschrijf dan ook GetHashCode, met HashCode.Combine(...) en dezelfde properties. Anders waarschuwt de compiler met CS0659, en vindt een Dictionary je object niet terug (uitleg).
  • Begin Equals met de test if (o is not Boek temp), die dan false teruggeeft, zoals in het boek (uitleg). Hoe is werkt, zie je in hoofdstuk 16. De groene kronkel CS8765 onder de parameter mag je laten staan.
  • Een abstract methode heeft geen body, en elke child moet ze overriden. Staat er één in je klasse, dan moet de klasse zelf ook abstract zijn (uitleg).
Tip

Nu je met overerving werkt, is het tijd om de ingebouwde “Class designer” van Visual Studio eens te bekijken: die tekent je klassen en hun overerving als schema. Deze kennisclip toont hoe je hem installeert en gebruikt: Class diagram en de class designer in Visual Studio.

Voorspel de uitvoer: wie woont er op kot? (Essential)

Je project heet Kot. Er zijn vier klassen, elk in een eigen bestand:

internal class Koelkast
{
    public int AantalBlikjes { get; set; }
}
internal class Kotgenoot
{
    public string Naam { get; set; }

    public string ToString()
    {
        return $"Kotgenoot {Naam}";
    }
}
internal class Afwasbeurt
{
    public string Wie { get; set; }

    public override string ToString()
    {
        Console.WriteLine($"{Wie} wast af");
        return "";
    }
}
internal class Pizza
{
    public string Smaak { get; set; }

    public override string ToString()
    {
        return $"Pizza {Smaak}";
    }

    public override bool Equals(object o)
    {
        if (o is not Pizza temp)
            return false;
        return Smaak == temp.Smaak;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Smaak);
    }
}

Deel 1. Schrijf op papier wat dit programma toont, lijn per lijn. Typ de code pas daarna over en vergelijk.

Twee dingen die je daarbij nodig hebt: niet enkel Console.WriteLine(object) roept ToString op, ook een object tussen accolades in $"..." doet dat. En C# stelt de volledige tekst tussen de haakjes van WriteLine eerst samen, en toont hem pas daarna.

Koelkast frigo = new Koelkast() { AantalBlikjes = 6 };
Console.WriteLine(frigo);
Console.WriteLine(frigo.GetType().Name);
Console.WriteLine("---");
Kotgenoot jonas = new Kotgenoot() { Naam = "Jonas" };
Console.WriteLine(jonas);
Console.WriteLine(jonas.ToString());
Console.WriteLine("---");
Afwasbeurt vandaag = new Afwasbeurt() { Wie = "Lotte" };
Console.WriteLine($"Planning: {vandaag}!");
Console.WriteLine("---");
Pizza mijne = new Pizza() { Smaak = "margherita" };
Pizza jouwe = new Pizza() { Smaak = "margherita" };
Pizza gedeeld = mijne;
Console.WriteLine(mijne == jouwe);
Console.WriteLine(mijne.Equals(jouwe));
Console.WriteLine(ReferenceEquals(mijne, jouwe));
Console.WriteLine(ReferenceEquals(mijne, gedeeld));
Console.WriteLine(mijne.Equals(frigo));

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Behalve CS8618 en CS8765, die je mag laten staan, geeft de compiler nog één waarschuwing. Welke, en bij welke klasse? Herstel ze. Wat toont de vierde regel van de uitvoer nu?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Herstel Afwasbeurt, zodat het programma Planning: Lotte wast af! toont.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Console.WriteLine(object) en $"{object}" roepen zelf ToString() op. Zonder override gebruiken ze de versie van System.Object, ook als je klasse een methode met dezelfde naam heeft.
  • ToString geeft tekst terug. Wie erin Console.WriteLine schrijft, krijgt de tekst op de verkeerde plaats.
  • == vergelijkt bij je eigen klassen de referenties, Equals vergelijkt wat jij beslist. Zie System.Object en ReferenceEquals().

Extra ToString aan bestaande projecten

Twee klassen die je al hebt, krijgen een ToString. Kopieer de klassen naar een nieuw project, dan hoef je je oude Main niet mee te nemen.

Deel 1: Pokémon. In de Pokémontester van hoofdstuk 10 gaf je je Pokemon-klasse een methode ShowInfo() die vier regels toont. Overschrijf ToString in Pokemon, zodat Console.WriteLine(pikachu); exact dezelfde vier regels toont. Gebruik \n om naar een nieuwe regel te gaan. ShowInfo() mag blijven, maar wordt één regel: ze roept ToString() op.

Waarom toont ShowInfo zelf iets, terwijl ToString niets toont?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2: Bookmark. Neem de klasse BookMark uit de Bookmark Manager van hoofdstuk 12 en HiddenBookMark uit HiddenBookmark van hoofdstuk 13. Overschrijf ToString in allebei:

  • een BookMark toont zijn naam, gevolgd door de URL tussen haakjes;
  • een HiddenBookMark toont hetzelfde, met daarachter een spatie en ---HIDDEN---.

Er mag geen dubbele code in HiddenBookMark staan. Tip: base.ToString().

Testcode. Zet deze code in je Main. Je code werkt als je exact deze uitvoer krijgt.

BookMark google = new BookMark() { Naam = "Google", URL = "https://www.google.com" };
HiddenBookMark reddit = new HiddenBookMark() { Naam = "Reddit", URL = "https://www.reddit.com" };
Console.WriteLine(google);
Console.WriteLine(reddit);
Google (https://www.google.com)
Reddit (https://www.reddit.com) ---HIDDEN---

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Voor wie nog wil. Pas ToonLijst in je Bookmark Manager aan: elke site verschijnt met haar nummer en daarna haar ToString, bijvoorbeeld 1. Google (https://www.google.com). Wat zie je bij een verborgen site?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • ToString geeft tekst terug. Wil je ze tonen, dan doe je dat buiten de methode.
  • Met base.ToString() staat de tekst van de parent op één plaats. Verandert BookMark ooit, dan verandert HiddenBookMark vanzelf mee.
  • Zie ToString() overriden.

Boek: de tweedehandsboekenbeurs (Essential)

Elk jaar verkoopt je studentenvereniging tweedehandse boeken: cursusboeken van vorig jaar, maar ook een roman of een fotoboek. Jij schrijft de klassen.

Deel 1. Maak een klasse Boek met:

  • drie auto-properties: ISBN (long), Titel en Auteur (allebei string);
  • een full property Prijs (double), met een private instantievariabele prijs.

Maak twee child-klassen die van Boek erven:

  • TekstBoek, een cursusboek, met een extra property Studiejaar (int);
  • KoffietafelBoek, zonder extra properties.

Elk soort boek heeft zijn eigen prijsgrenzen. Een TekstBoek kost tussen 20 en 80 euro, een KoffietafelBoek tussen 35 en 100 euro. Overschrijf daarvoor in beide child-klassen de property Prijs:

  • maak Prijs virtual in Boek;
  • in de child schrijf je public override double Prijs. De get geeft base.Prijs terug. De set controleert de waarde en doet daarna base.Prijs = value;;
  • ligt de prijs buiten de grenzen, dan werpt de set een exception op: throw new ArgumentException("Een cursusboek kost tussen 20 en 80 euro.");, of "Een koffietafelboek kost tussen 35 en 100 euro.". De prijs verandert dan niet.

Overschrijf ToString in Boek, zodat een boek zich zo toont: Titel - Auteur (ISBN) - Prijs euro.

Deel 2. Twee boeken kunnen samen verkocht worden als omnibus. Schrijf in Boek een static methode TelOp die twee Boek-objecten als parameter krijgt en een nieuw Boek teruggeeft:

  • de titel is Omnibus: gevolgd door de twee titels, met + ertussen;
  • de auteur is de twee auteurs, gescheiden door een komma en een spatie;
  • de prijs is de som van beide prijzen, gedeeld door 2;
  • het ISBN blijft 0: een omnibus krijgt pas een ISBN als hij gedrukt wordt.

Een omnibus is een gewoon Boek, geen TekstBoek of KoffietafelBoek. De prijsgrenzen gelden voor hem dus niet.

Testcode. Zet deze code in je Main. Je code werkt als je exact deze uitvoer krijgt.

Boek roman = new Boek() { ISBN = 9789000000011, Titel = "Het Kot", Auteur = "Lies Peeters", Prijs = 15 };
TekstBoek cursus = new TekstBoek() { ISBN = 9781234567897, Titel = "Programmeren in C#", Auteur = "Jan Janssens", Studiejaar = 1, Prijs = 30 };
KoffietafelBoek fotoboek = new KoffietafelBoek() { ISBN = 9789000000028, Titel = "Antwerpen van boven", Auteur = "Mo Benali", Prijs = 45 };
Console.WriteLine(roman);
Console.WriteLine(cursus);
Console.WriteLine(fotoboek);
Console.WriteLine(Boek.TelOp(roman, fotoboek));
try
{
    cursus.Prijs = 10;
}
catch (ArgumentException e)
{
    Console.WriteLine(e.Message);
}
Console.WriteLine(cursus);
try
{
    fotoboek.Prijs = 150;
}
catch (ArgumentException e)
{
    Console.WriteLine(e.Message);
}
Het Kot - Lies Peeters (9789000000011) - 15 euro
Programmeren in C# - Jan Janssens (9781234567897) - 30 euro
Antwerpen van boven - Mo Benali (9789000000028) - 45 euro
Omnibus: Het Kot + Antwerpen van boven - Lies Peeters, Mo Benali (0) - 30 euro
Een cursusboek kost tussen 20 en 80 euro.
Programmeren in C# - Jan Janssens (9781234567897) - 30 euro
Een koffietafelboek kost tussen 35 en 100 euro.
Tip

PRO: in de appendix lees je hoe je de +-operator zelf kan schrijven, zodat roman + fotoboek een omnibus geeft.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Ook een property kan je overschrijven. Met base.Prijs blijft de prijs in de private instantievariabele van Boek staan, en voegt de child enkel een controle toe.
  • Een set die een foute waarde stil negeert, verbergt een bug. Met throw merkt wie de klasse gebruikt meteen dat er iets mis is. Zie Zelf exceptions maken.
  • Een long toont geen voorloopnullen: een ISBN dat met 0 begint, verliest die nul.

De boekenbeurs telt (Essential)

Op de boekenbeurs brengen Lotte en Jonas allebei hun cursus “Programmeren in C#” binnen: hetzelfde ISBN, maar een andere prijs. Voor de beurs is dat hetzelfde boek, twee keer. Werk verder met de klassen uit Boek: de tweedehandsboekenbeurs.

Deel 1. Twee boeken zijn gelijk als hun ISBN gelijk is. Overschrijf Equals in Boek. Begin zoals in het boek met de test if (o is not Boek temp), die dan false teruggeeft. Schrijf nog geen GetHashCode, ook al zegt het blok bovenaan deze pagina dat het hoort: in deel 2 zie je waarom.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

TekstBoek vanLotte = new TekstBoek() { ISBN = 9781234567897, Titel = "Programmeren in C#", Auteur = "Jan Janssens", Studiejaar = 1, Prijs = 30 };
TekstBoek vanJonas = new TekstBoek() { ISBN = 9781234567897, Titel = "Programmeren in C#", Auteur = "Jan Janssens", Studiejaar = 1, Prijs = 25 };
KoffietafelBoek vanMira = new KoffietafelBoek() { ISBN = 9789000000028, Titel = "Antwerpen van boven", Auteur = "Mo Benali", Prijs = 45 };
Console.WriteLine(vanLotte == vanJonas);
Console.WriteLine(vanLotte.Equals(vanJonas));
Console.WriteLine(vanLotte.Equals(vanMira));
False
True
False

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. De beurs houdt bij hoeveel exemplaren er van elk boek liggen, in een Dictionary<Boek, int>: de sleutel is het boek, de waarde het aantal exemplaren. Voeg deze code toe aan je Main. ContainsKey zegt of een sleutel al in de Dictionary zit.

Boek[] binnengebracht = { vanLotte, vanMira, vanJonas };
Dictionary<Boek, int> voorraad = new Dictionary<Boek, int>();
foreach (Boek boek in binnengebracht)
{
    if (voorraad.ContainsKey(boek))
    {
        voorraad[boek]++;
    }
    else
    {
        voorraad.Add(boek, 1);
    }
}
foreach (var item in voorraad)
{
    Console.WriteLine($"{item.Value} x {item.Key.Titel}");
}

Je verwacht 2 x Programmeren in C#. Wat toont het programma? Welke waarschuwing gaf de compiler al bij Boek? Herstel Boek, zodat de voorraad klopt.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Wie Equals overschrijft, overschrijft ook GetHashCode, met dezelfde properties. Twee gelijke objecten moeten dezelfde hashcode hebben.
  • De fout zie je pas als een Dictionary je object als sleutel gebruikt. De waarschuwing CS0659 zag het al eerder: lees je waarschuwingen.
  • Zie Equals() overriden en GetHashCode() overriden.

Geometrische figuren (Essential)

Een “geometrische figuur” kan je niet tekenen, een rechthoek wel. Dat is het voorbeeld waarmee het boek abstracte klassen uitlegt.

Maak een abstracte klasse GeometrischeFiguur met:

  • twee properties Hoogte en Breedte (int), met een private set;
  • een constructor met twee parameters: eerst de hoogte, dan de breedte. Ook een abstracte klasse mag een constructor hebben. Met new kan je hem niet oproepen, maar een child roept hem op met base(...), zoals in hoofdstuk 13;
  • een abstracte methode BerekenOppervlakte() die een double teruggeeft;
  • een read-only property Oppervlakte, waarvan de get het resultaat van BerekenOppervlakte() teruggeeft;
  • een abstracte read-only property AantalHoeken (int);
  • een override van ToString, die de naam van de klasse, de breedte, de hoogte, het aantal hoeken en de oppervlakte toont, zoals in de testcode hieronder. De naam van de klasse vraag je op met GetType().Name.
Waarschuwing

Oppervlakte heeft enkel een get. Met een set kon iemand de oppervlakte aanpassen zonder de hoogte of de breedte te veranderen, en dan klopt ze niet meer.

Maak drie klassen:

  • Rechthoek erft van GeometrischeFiguur. De oppervlakte is breedte maal hoogte, en een rechthoek heeft 4 hoeken.
  • Vierkant erft van Rechthoek. Het heeft één constructor, met enkel de lengte van een zijde. Vierkant berekent zelf niets: de oppervlakte en het aantal hoeken erft het van Rechthoek.
  • Driehoek erft van GeometrischeFiguur. De oppervlakte is breedte maal hoogte gedeeld door 2.0, en een driehoek heeft 3 hoeken.

Herinner je de strikvraag over Vierkant en Rechthoek in Is een of heeft een? Hier mag het wel: hoogte en breedte hebben een private set, dus na het aanmaken kan een vierkant niet meer van vorm veranderen.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

List<GeometrischeFiguur> figuren = new List<GeometrischeFiguur>();
figuren.Add(new Rechthoek(3, 4));
figuren.Add(new Vierkant(4));
figuren.Add(new Driehoek(3, 5));
double totaal = 0;
foreach (GeometrischeFiguur figuur in figuren)
{
    Console.WriteLine(figuur);
    totaal += figuur.Oppervlakte;
}
Console.WriteLine($"Samen: {totaal}");
Rechthoek van 4 op 3: 4 hoeken, oppervlakte 12
Vierkant van 4 op 4: 4 hoeken, oppervlakte 16
Driehoek van 5 op 3: 3 hoeken, oppervlakte 7,5
Samen: 35,5

Rechthoek van 4 op 3 betekent: 4 breed en 3 hoog.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een abstracte klasse mag gewone code bevatten, zoals Oppervlakte en ToString, die abstracte leden gebruikt. Elke child vult enkel de gaten in.
  • Ook een property kan abstract zijn: public abstract int AantalHoeken { get; } heeft geen body, en de child moet ze overriden. Zie Abstracte properties.
  • GetType().Name geeft de naam van de echte klasse van het object, ook als de code in de parent staat. Zie GetType().

Stevens abstracte bouwwerf (Essential)

Stagiair Steven schrijft een programma voor een bouwbedrijf. Hij liet een A.I. de klassen maken en plakte alles in zijn project zonder het na te lezen. Niets compileert. Om plaats te sparen staan de klassen van een fragment hier onder elkaar. In je eigen project zet je elke klasse in een apart bestand.

Fragment 1.

internal abstract class Bouwmachine
{
    public int Gewicht { get; set; }
}

internal class Graafmachine : Bouwmachine
{
    public int Bakinhoud { get; set; }
}

// in Main
Bouwmachine machine = new Bouwmachine();
machine.Gewicht = 12000;

Fragment 2.

internal abstract class Materiaal
{
    public double Hoeveelheid { get; set; }

    public abstract double BerekenPrijs()
    {
        return 0;
    }
}

internal class Zand : Materiaal
{
    public override double BerekenPrijs()
    {
        return Hoeveelheid * 45;
    }
}

Fragment 3.

internal class Werknemer
{
    public int Uren { get; set; }

    public abstract double BerekenLoon();
}

internal class Metser : Werknemer
{
    public override double BerekenLoon()
    {
        return Uren * 32.5;
    }
}

Fragment 4.

internal abstract class Gebouw
{
    public int Verdiepingen { get; set; }

    public abstract int BerekenBouwtijd();
}

internal class Rijwoning : Gebouw
{
    public int BerekenBouwTijd()
    {
        return Verdiepingen * 8;
    }
}

Fragment 5.

internal sealed class Werfkeet
{
    public int Zitplaatsen { get; set; }
}

internal class LuxeWerfkeet : Werfkeet
{
    public bool HeeftDouche { get; set; }
}

Deel 1. Steven zette elk fragment in een apart project. Dit zijn de vijf meldingen die hij kreeg, door elkaar:

  • A. CS0534 'Rijwoning' does not implement inherited abstract member 'Gebouw.BerekenBouwtijd()'
  • B. CS0509 'LuxeWerfkeet': cannot derive from sealed type 'Werfkeet'
  • C. CS0144 Cannot create an instance of the abstract type or interface 'Bouwmachine'
  • D. CS0513 'Werknemer.BerekenLoon()' is abstract but it is contained in non-abstract type 'Werknemer'
  • E. CS0500 'Materiaal.BerekenPrijs()' cannot declare a body because it is marked abstract

Koppel elke melding aan een fragment. Zoek daarna in dat fragment de lijn die het probleem veroorzaakt, en zeg in je eigen woorden welke regel Steven brak. Doe het op papier, zonder Visual Studio.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Herstel elk fragment met zo weinig mogelijk aanpassingen, zodat het compileert en doet wat Steven bedoelde. Test elk fragment in een eigen project.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Voor wie nog wil. Stevens namespace heet Bouwwerf, dus kraan.GetType() toont Bouwwerf.Kraan. Steven wil Kraan van 40 meter op het scherm. De A.I. stelde dit voor:

internal class Kraan
{
    public int Hoogte { get; set; }

    public override string GetType()
    {
        return "Kraan";
    }
}

// in Main
Kraan kraan = new Kraan();
kraan.Hoogte = 40;
Console.WriteLine($"{kraan.GetType()} van {kraan.Hoogte} meter");

Waarom compileert dit niet? Hoe krijgt Steven wel Kraan van 40 meter op het scherm?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Elke melding noemt tussen aanhalingstekens de klasse of methode waar het misloopt. Daar begin je te zoeken, maar de fout staat niet altijd op die plek: CS0534 wijst naar Rijwoning, en het probleem was één hoofdletter in de naam van de methode.
  • Zet je de vijf fragmenten samen in één project, dan meldt de compiler er maar vier. CS0144 verschijnt pas als de andere vier opgelost zijn. Krijg je na een herstelling een nieuwe melding, dan zat die fout er dus al.
  • Zie Abstracte klassen in C#, Abstracte methoden, sealed en GetType().

Money, money, money (Essential)

Maak enkele klassen die een bank kan gebruiken:

  1. Een abstracte klasse Rekening. Het saldo staat in een private instantievariabele, die je enkel via een read-only property Saldo kan uitlezen. Twee methoden passen het saldo aan: VoegGeldToe(double bedrag) en HaalGeldAf(double bedrag). HaalGeldAf geeft een bool terug: false als er niet genoeg geld op de rekening staat (dan verandert het saldo niet), anders true. Verder is er een abstracte methode BerekenRente() die de rente als double teruggeeft.
  2. Een klasse BankRekening die een Rekening is. De rente van een BankRekening is 5% als het saldo hoger is dan 100 euro, anders 0.
  3. Een klasse SpaarRekening die een Rekening is. De rente van een SpaarRekening is altijd 2%.
  4. Een klasse ProRekening die een SpaarRekening is. Ze berekent de rente zoals een SpaarRekening (base.BerekenRente()), en telt er per volle 1000 euro saldo nog 10 euro bij. Bij 2500 euro is dat 50 + 20 = 70 euro.

Testcode. Je code werkt als je exact deze uitvoer krijgt.

BankRekening bank = new BankRekening();
bank.VoegGeldToe(80);
Console.WriteLine($"BankRekening met {bank.Saldo} euro: rente {bank.BerekenRente()} euro");
bank.VoegGeldToe(120);
Console.WriteLine($"BankRekening met {bank.Saldo} euro: rente {bank.BerekenRente()} euro");

SpaarRekening spaar = new SpaarRekening();
spaar.VoegGeldToe(500);
Console.WriteLine($"SpaarRekening met {spaar.Saldo} euro: rente {spaar.BerekenRente()} euro");

ProRekening pro = new ProRekening();
pro.VoegGeldToe(2500);
Console.WriteLine($"ProRekening met {pro.Saldo} euro: rente {pro.BerekenRente()} euro");

if (!pro.HaalGeldAf(3000))
{
    Console.WriteLine("Zoveel geld staat er niet op de ProRekening.");
}
BankRekening met 80 euro: rente 0 euro
BankRekening met 200 euro: rente 10 euro
SpaarRekening met 500 euro: rente 10 euro
ProRekening met 2500 euro: rente 70 euro
Zoveel geld staat er niet op de ProRekening.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Rekening regelt wat elke rekening heeft: het saldo en de twee methoden. Wat per soort verschilt, de rente, is abstract: elke child moet het invullen.
  • ProRekening erft van SpaarRekening, niet van Rekening. Zo haalt base.BerekenRente() de 2% op, en staat die berekening maar één keer in je code.
  • Zie Abstracte methoden.

Saldo tekort (Essential)

Het is de 28e van de maand. Je wil 80 euro afhalen voor het weekend, maar er staat nog maar 50 euro op je rekening. Nu geeft HaalGeldAf uit Money, money, money gewoon false terug, en wie dat vergeet te controleren, merkt niets. Een eigen exception zegt het luider.

Deel 1. Maak een klasse SaldoTekortException die erft van Exception, met de drie standaardconstructors uit het boek (Een eigen exception ontwerpen).

Pas daarna HaalGeldAf in Rekening aan: ze wordt void. Staat er niet genoeg geld op de rekening, dan werpt ze een SaldoTekortException op met de boodschap Onvoldoende saldo: je komt ... euro tekort.. Het saldo verandert dan niet.

Testcode. De testcode van Money, money, money compileert nu niet meer, want HaalGeldAf geeft niets meer terug. Vervang ze door deze code. Je code werkt als je exact deze uitvoer krijgt.

SpaarRekening rekening = new SpaarRekening();
rekening.VoegGeldToe(50);
try
{
    rekening.HaalGeldAf(80);
}
catch (SaldoTekortException e)
{
    Console.WriteLine(e.Message);
}
Console.WriteLine($"Saldo: {rekening.Saldo} euro");
Onvoldoende saldo: je komt 30 euro tekort.
Saldo: 50 euro

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Haal in de tweede constructor : base(message) weg, zodat er public SaldoTekortException(string message) { } staat. Het compileert nog. Voer de testcode uit. Wat toont ze nu, en waarom?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een eigen exception is een gewone klasse die van Exception erft. De naam zegt al wat er misliep.
  • De boodschap geef je door aan de parent met : base(message), net als bij elke andere constructor die iets van zijn parent nodig heeft.
  • Met catch (SaldoTekortException e) vang je enkel die ene fout op. Zie Zelf exceptions maken.

Dierentuin (Essential)

Maak een console-applicatie met een abstracte klasse Dier. Elk dier heeft een gewicht (int) en een abstracte methode void Zegt() die het geluid van dat dier op het scherm toont. Maak enkele child-klassen die van Dier erven en Zegt overriden, bijvoorbeeld Koe, Slang en Varken. Die drie heb je nodig in Dierentuin advanced, in hoofdstuk 16.

Potloodtekening: de robot als dierenverzorger wandelt langs een leeuw, een olifant, een eend en een aap die elk hun eigen geluid maken.

Zet enkele dieren in een List<Dier>. Daarna verschijnt een menu dat blijft terugkomen tot de gebruiker q kiest:

  1. Dier verwijderen: de gebruiker geeft in het hoeveelste dier uit de lijst weg moet.
  2. Gemiddeld gewicht: het gemiddelde gewicht van alle dieren, met twee cijfers na de komma.
  3. Dieren praten: van elk dier wordt Zegt() opgeroepen.
  4. Opnieuw beginnen: de lijst wordt leeggemaakt en opnieuw gevuld met de dieren van bij de start.

Schrijf voor de menu-items 1, 2 en 3 een methode, met de lijst als parameter. Ook het vullen van de lijst komt in een methode, want je hebt het twee keer nodig: bij de start en bij “opnieuw beginnen”.

Een lege lijst mag je programma niet laten crashen: kies je dan 1 of 2, dan toont het programma Er zijn geen dieren meer. Een nummer dat niet in de lijst staat, geeft Dat dier bestaat niet. Je mag ervan uitgaan dat de gebruiker een getal typt.

Een voorbeeld, met een koe van 600 kg, een slang van 3 kg en een varken van 110 kg:

1. Dier verwijderen
2. Gemiddeld gewicht
3. Dieren praten
4. Opnieuw beginnen
q. Stoppen
>3
Moeeeh
Sssssss
Knor knor
...
>2
Gemiddeld gewicht: 237,67 kg
...
>1
Het hoeveelste dier moet weg (1 tot en met 3)?
>2
...
>3
Moeeeh
Knor knor

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Van Dier kan je geen object maken, maar een List<Dier> kan wel koeien, slangen en varkens bevatten. dier.Zegt() laat bij elk dier zijn eigen geluid horen. Waarom dat werkt, zie je in hoofdstuk 16.
  • Omdat Zegt abstract is, kan geen enkele child vergeten een geluid te maken.
  • Het vullen van de lijst staat in een methode, want je hebt het twee keer nodig.

Meme Lord Simulator (Final Essentials) (GPT)

Het internet is wild en jij bent de curator. Bouw een systeem dat de virale waarde van verschillende soorten memes berekent.

Deel 1: de abstracte klasse. Maak een abstracte klasse Meme met:

  • drie properties: Auteur (string), de maker van de meme; Beschrijving (string), wat er op de afbeelding staat; en BasisLikes (int), het aantal likes dat ze standaard krijgt;
  • een abstracte methode BerekenMemeWaarde() die een double teruggeeft;
  • een virtual read-only property IsCringe (bool). Standaard is een meme cringe als BasisLikes kleiner is dan 100;
  • een override van ToString die [Auteur] post: [Beschrijving] - Waarde: [waarde] - Status: [status] toont. De status is Cringe als de meme cringe is, anders Dank.

Deel 2: de soorten. Maak drie klassen die van Meme erven:

  1. DankMeme (hoge kwaliteit, niche humor) heeft een extra property IronieNiveau (int, van 1 tot 10). De waarde is BasisLikes maal IronieNiveau. Een dank meme is nooit cringe, tenzij het ironieniveau kleiner is dan 3.
  2. NormieMeme (Minion-quotes, 9gag-reposts) is BasisLikes maal 0,5 waard, en is altijd cringe.
  3. DeepFriedMeme (onleesbaar, luid en vervormd) heeft een extra property Ruisfactor (int, van 1 tot 100). De waarde is BasisLikes plus Ruisfactor maal 10. Zijn ToString zet de volledige tekst van de ToString van Meme in hoofdletters en plakt er 🔥💀🅱️ achter.

De grenzen van IronieNiveau en Ruisfactor hoef je niet te controleren.

Zonder Console.OutputEncoding = System.Text.Encoding.UTF8; bovenaan je Main toont de console vraagtekens in plaats van de emoji.

Deel 3: de feed. Geef de klasse Meme een static methode GenereerFeed(List<Meme> memes). Ze overloopt de lijst:

  • een cringe meme toont ze als Cringe verwijderd: gevolgd door de meme. Ze haalt hem niet uit de lijst: “verwijderd” betekent enkel dat hij niet in de feed komt;
  • elke andere meme toont ze gewoon;
  • op het einde toont ze Gemiddelde waarde van de feed:, het gemiddelde van de memes die niet cringe waren. Was elke meme cringe, dan toont ze Geen enkele meme overleefde de feed.

Maak in Main een lijst met memes van alle soorten, met wat cringe ertussen, en roep Meme.GenereerFeed op. Met deze memes:

memes.Add(new DankMeme() { Auteur = "Tim", Beschrijving = "Kat met zonnebril", BasisLikes = 40, IronieNiveau = 8 });
memes.Add(new NormieMeme() { Auteur = "Lies", Beschrijving = "Minion op maandag", BasisLikes = 2000 });
memes.Add(new DankMeme() { Auteur = "Jonas", Beschrijving = "Examenrooster als horrorfilm", BasisLikes = 300, IronieNiveau = 2 });
memes.Add(new DeepFriedMeme() { Auteur = "Ann", Beschrijving = "Schreeuwende eend", BasisLikes = 200, Ruisfactor = 50 });
memes.Add(new DeepFriedMeme() { Auteur = "Mo", Beschrijving = "Wazige frituur", BasisLikes = 20, Ruisfactor = 90 });

krijg je:

Tim post: Kat met zonnebril - Waarde: 320 - Status: Dank
Cringe verwijderd: Lies post: Minion op maandag - Waarde: 1000 - Status: Cringe
Cringe verwijderd: Jonas post: Examenrooster als horrorfilm - Waarde: 600 - Status: Cringe
ANN POST: SCHREEUWENDE EEND - WAARDE: 700 - STATUS: DANK 🔥💀🅱️
Cringe verwijderd: MO POST: WAZIGE FRITUUR - WAARDE: 920 - STATUS: CRINGE 🔥💀🅱️
Gemiddelde waarde van de feed: 510

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • abstract of virtual? BerekenMemeWaarde is abstract, want er is geen standaardwaarde: elke soort moet ze zelf invullen. IsCringe is virtual, want er is een standaard die een child mag overschrijven.
  • base.ToString().ToUpper() hergebruikt de tekst van Meme. DeepFriedMeme bouwt niets opnieuw op.
  • Eén List<Meme> volstaat voor alle soorten. Een element tijdens een foreach uit de lijst halen gaat niet: dat crasht met “Collection was modified”.