• Let op Kopieer geen code van de parent naar de child. Wat de parent al doet, roep je op met base.Methode() of base(...), en wat de child erft, declareer je niet opnieuw (boeteblad).
  • Een methode overschrijven doe je met virtual in de parent en override in de child. Meldt de compiler hides inherited member, dan ben je override vergeten: je programma start, maar via een variabele van het parent-type draait de versie van de parent (uitleg).
  • Heeft de parent enkel een constructor met parameters, dan roept elke child die op met base(...) achter haar constructorkop (uitleg).
  • Moet de child aan iets van de parent, maak het dan protected, niet public. Bij een property volstaat { get; protected set; } (uitleg).

Is een of heeft een?

Voor je een klasse laat erven, stel je de vraag uit het boek: is een X een Y? Bekijk de tien paren hieronder. Kies telkens:

  • overerving: de eerste klasse is een tweede, dus class Eerste : Tweede;
  • heeft een: de ene klasse heeft een andere, en dat is geen overerving;
  • geen van beide.

Schrijf er telkens in één zin bij waarom. Twee paren zijn strikvragen.

  1. Student en Persoon
  2. Kot en Koelkast
  3. Lector en Persoon
  4. Fiets en Bel
  5. Klas en Student
  6. Elixer en Drank
  7. Fiets en Auto
  8. Pinguin en Vogel, als de klasse Vogel een methode Vlieg() heeft
  9. Vierkant en Rechthoek, als de klasse Rechthoek een methode MaakBreder(int cm) heeft die enkel de breedte verandert
  10. Pegasus, het vliegende paard uit de Griekse mythen, en de klassen Paard en Vogel: mag class Pegasus : Paard, Vogel?

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.

  • Overerving past bij “is een”. Bij “heeft een” krijgt een klasse een instantievariabele of property van de andere klasse, daar komt hoofdstuk 15 op terug.
  • Ook een “is een” die in gewone taal klopt, controleer je: kan de child echt alles wat de parent kan?
  • Zie Is-een relatie en Multiple inheritance.

Magische dranken (Essential) (GPT)

In deze opdracht ontwerp je een systeem waarin dranken niet alleen dorst lessen, maar ook magische krachten hebben. Van gewone drankjes tot zeldzame elixers: elke slok telt!

Potloodtekening: het stokmannetje met tovenaarshoed brouwt een drankje, de robot drinkt een rood elixir en er springen vonken uit zijn antenne.

  • Maak een klasse Drank met:
    • een property Naam;
    • een constructor die de naam instelt;
    • een virtual methode BerekenKracht() die 50 teruggeeft.
  • Maak een klasse Elixer die erft van Drank, met een extra property IsZeldzaam die je via de constructor instelt.
    • Overschrijf BerekenKracht(): vraag eerst de basiskracht op met base.BerekenKracht() en tel er een bonus bij: 20 als het elixer zeldzaam is, anders 10.

Krijg je bij de constructor van Elixer de fout CS7036 There is no argument given that corresponds to the required parameter 'naam' of 'Drank.Drank(string)'? Lees dan in het boek wat base(...) doet bij constructors.

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

Drank cola = new Drank("Gewone cola");
Elixer mystiek = new Elixer("Mystiek elixer", true);
Elixer standaard = new Elixer("Standaard elixer", false);

Console.WriteLine($"{cola.Naam} heeft kracht {cola.BerekenKracht()}");
Console.WriteLine($"{mystiek.Naam} heeft kracht {mystiek.BerekenKracht()}");
Console.WriteLine($"{standaard.Naam} heeft kracht {standaard.BerekenKracht()}");
Gewone cola heeft kracht 50
Mystiek elixer heeft kracht 70
Standaard elixer heeft kracht 60

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.

  • Met base.BerekenKracht() gebruik je de 50 van de parent in plaats van ze over te typen. Verandert de basiskracht ooit, dan pas je ze op één plaats aan.
  • Heeft de parent geen constructor zonder parameters, dan roept de child er een op met base(...).

Een avond karaoke (Essential)

Na de laatste examendag huur je met je vrienden een karaokebox. De zaak rekent 50 euro voor de box, plus 20 euro per uur. Toon je een studentenkaart, dan krijg je 10% korting op het totaal.

Deel 1. Maak een klasse Boeking met:

  • een property Naam, op wiens naam de box geboekt is, en een property Uren (int);
  • een constructor die beide instelt;
  • een virtual methode BerekenKost() die de totale kost als double teruggeeft: 50 euro plus 20 euro per uur. Zet de twee bedragen in een const;
  • een methode ToonInfo() die de naam, het aantal uur en de kost toont, zoals in de testcode van deel 2.

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. Maak een klasse StudentenBoeking die erft van Boeking. Ze heeft alles wat een gewone boeking heeft, maar ze geeft 10% korting op de kost.

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

Boeking jos = new Boeking("Jos", 3);
jos.ToonInfo();
StudentenBoeking mira = new StudentenBoeking("Mira", 3);
mira.ToonInfo();
Boeking kot = new StudentenBoeking("Kot Zuidstraat", 1);
kot.ToonInfo();
Jos: 3 uur, 110 euro
Mira: 3 uur, 99 euro
Kot Zuidstraat: 1 uur, 63 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 3. Aan ToonInfo heb je in StudentenBoeking niets veranderd. Waarom toont Mira dan toch 99 euro en geen 110? En waarom toont kot 63 euro, terwijl de variabele van het type Boeking is?

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 child overschrijft enkel wat anders is. ToonInfo hoeft niet mee, want ze gebruikt BerekenKost, en die is virtual.
  • Een object van een child-klasse mag in een variabele van het parent-type. De override draait dan toch. Het waarom komt in hoofdstuk 16.
  • Zie Het base keyword.

Het dierenrijk

Klassenschema van het dierenrijk: Dier bovenaan; Zoogdier en Reptiel erven van Dier; Konijn, Koe en Hond erven van Zoogdier; Slang en Leguaan erven van Reptiel.

Bouw deze klassen na. Dier is de parent van Zoogdier en Reptiel, Zoogdier is de parent van Konijn, Koe en Hond, en zo verder. Hond en Leguaan zijn optioneel.

  1. Geef elke klasse één property die haar parent niet heeft. Bijvoorbeeld: Dier krijgt Naam en IsUitgestorven, Zoogdier krijgt Biotoop, Reptiel krijgt AantalSchubben. Verzin de rest zelf.
  2. Geef Dier een virtual methode ToonInfo() die zijn properties toont. Elke child overschrijft ToonInfo(): ze roept eerst base.ToonInfo() op en toont daarna enkel haar eigen property.
  3. Je maakt een Konijn aan en roept ToonInfo() op. Schrijf eerst op papier welke regels verschijnen, in welke volgorde, en uit welke klasse elke regel komt. Probeer het daarna uit.
  4. Maak van elke klasse een object, zet ze allemaal in een List<Dier> en roep in een foreach bij elk dier ToonInfo() op. Welke versie van ToonInfo draait er bij de koe?

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 override begint met base.ToonInfo() en voegt één regel toe. Zo staat elke property op één plaats, ook over drie niveaus.
  • base roept de parent op, en die roept op haar beurt haar eigen parent op.
  • Zie Het base keyword.

Voorspel de uitvoer: de repetitie van de studentenband (Essential)

Je studentenband schrijft een programma om de instrumenten klaar te zetten voor de repetitie. Er zijn drie klassen, elk in een eigen bestand:

internal class Instrument
{
    public Instrument()
    {
        Console.WriteLine("Instrument gestemd");
    }

    public Instrument(int snaren)
    {
        Console.WriteLine($"Instrument met {snaren} snaren");
    }

    public virtual void Speel()
    {
        Console.WriteLine("Plink");
    }
}
internal class Gitaar : Instrument
{
    public Gitaar() : base(6)
    {
        Console.WriteLine("Gitaar klaar");
    }

    public override void Speel()
    {
        Console.WriteLine("Tokkel");
        base.Speel();
    }
}
internal class ElektrischeGitaar : Gitaar
{
    public ElektrischeGitaar()
    {
        Console.WriteLine("Versterker aan");
    }
}

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

Instrument triangel = new Instrument();
Console.WriteLine("---");
Instrument gitaar = new ElektrischeGitaar();
Console.WriteLine("---");
gitaar.Speel();
Console.WriteLine("---");
triangel.Speel();

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 de constructor zonder parameters uit Instrument weg. Hoeveel fouten geeft de compiler, en waar? Waarom geeft ElektrischeGitaar geen fout?

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. Zet de constructor terug. Er mag geen klasse meer van Gitaar erven. Welk keyword zet je bij Gitaar, en wat zegt de compiler dan?

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.

  • Bij het aanmaken van een object draaien de constructors van boven naar beneden: eerst de verste voorouder, als laatste de klasse zelf.
  • Zonder base(...) draait de constructor zonder parameters van de parent. Met base(...) kies je zelf welke.
  • base.Speel() mag overal in een override staan, ook na je eigen code.
  • Zie Volgorde van constructors en sealed.

Stevens dierentuin (Essential)

Stagiair Steven werkt voor de dierentuin. Hij moest een klasse Dier schrijven en een klasse Leeuw die ervan erft: een leeuw eet anders en maakt een ander geluid dan een gewoon dier. Een A.I. leverde dit, en Steven plakte het zonder nalezen in zijn project.

De klasse Dier:

internal class Dier
{
    private int kiloVoerPerDag;

    public string Naam { get; private set; }

    public Dier(string naam, int kiloVoer)
    {
        Naam = naam;
        kiloVoerPerDag = kiloVoer;
    }

    public void Eet()
    {
        Console.WriteLine($"{Naam} eet {kiloVoerPerDag} kg voer.");
    }

    public virtual void MaakGeluid()
    {
        Console.WriteLine($"{Naam} maakt geluid.");
    }
}

De klasse Leeuw:

internal class Leeuw : Dier
{
    public bool IsMannetje { get; private set; }

    public Leeuw(string naam, int kiloVlees, bool isMannetje)
    {
        IsMannetje = isMannetje;
    }

    public override void Eet()
    {
        Console.WriteLine($"{Naam} verslindt {kiloVoerPerDag} kg vlees.");
    }

    public void MaakGeluid()
    {
        if (IsMannetje)
        {
            Console.WriteLine($"{Naam} brult: ROAAAR!");
        }
        else
        {
            Console.WriteLine($"{Naam} gromt.");
        }
    }
}

En in Main:

Dier kameel = new Dier("Kamiel", 12);
Dier leeuw = new Leeuw("Simba", 7, true);

kameel.Eet();
kameel.MaakGeluid();
leeuw.Eet();
leeuw.MaakGeluid();

Deel 1. Zet elke klasse in een eigen bestand en compileer. De compiler meldt één fout en één waarschuwing. Los de fout op. De waarschuwing laat je voorlopig staan.

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. Compileer opnieuw. Nu verschijnen er twee nieuwe fouten. Los ze allebei op, zonder Dier een extra constructor te geven.

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. Het programma compileert nu, met enkel nog de waarschuwing van deel 1. Voer het uit. Welke lijn klopt niet? Zoek de vierde fout. Wat had de waarschuwing je al verteld?

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.

  • De compiler meldt niet alle fouten tegelijk. Los de fout op die je ziet, en compileer opnieuw.
  • virtual en override horen per methode bij elkaar. De A.I. zette virtual bij MaakGeluid en override bij Eet, en dus klopte geen van beide.
  • Een waarschuwing laat je programma starten, maar CS0114 wees hier net naar de enige fout die de compiler niet tegenhield. Zie de waarschuwing over hiding in Een voorbeeld met vliegende objecten.
  • Test een override via een variabele van het type van de parent. Via Leeuw zie je hiding niet.
  • Zie ook protected keyword en Overloaded constructors en base().

HiddenBookmark

Soms wil je een site openen zonder dat je kotgenoot over je schouder meekijkt welke. Bouw daarom verder op je Bookmark Manager uit hoofdstuk 12, met de klasse BookMark.

Deel 1. Maak een klasse HiddenBookMark die erft van BookMark. Ze overschrijft ToonSite: vóór en na de site toont ze de regel **********INCOGNITO MODE************. Wat moet je daarvoor in BookMark aanpassen?

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. Pas VraagBookmark aan: na de naam en de URL vraagt ze Verborgen (j/n)?. Bij j geeft ze een HiddenBookMark terug, anders een gewone BookMark. De rest van je programma, met de List<BookMark>, blijft zoals het was.

Open daarna een verborgen site. Waarom verschijnen de incognito-regels, terwijl de lijst een List<BookMark> is? En open ook een verborgen site met een URL zonder https://: waarom zie je de incognito-regel dan maar één keer?

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.

  • base.ToonSite() staat hier in het midden, met code ervoor en erna. Zie Het base keyword.
  • Een bestaande klasse uitbreiden vraagt maar één aanpassing in die klasse: virtual. De rest staat in de nieuwe child.
  • Gooit base.ToonSite() een exception, dan wordt de rest van de override overgeslagen, net als bij een gewone methode.

Pakjes per drone (Final Essentials) (GPT)

De campusbib levert boeken voortaan per drone. Jij schrijft de software die de vloot laat vliegen.

Deel 1: de klasse Drone. Maak een klasse Drone met:

  • een property Model (string) en een property Batterij (int), allebei met een private set;
  • een constructor met het model als parameter. De batterij start op 100;
  • een protected virtual methode BerekenVerbruik() die teruggeeft hoeveel procent batterij één vlucht kost: 5;
  • een protected virtual methode Beschrijving() die de tekst vliegt rond teruggeeft;
  • een gewone methode Vlieg(), dus niet virtual. Ze trekt het verbruik af van de batterij (nooit onder 0) en toont één regel: [Model] [Beschrijving]. Batterij: [Batterij]%. Is de batterij daarna lager dan 20, dan toont ze ook Waarschuwing: batterij bijna leeg!.

BerekenVerbruik en Beschrijving zijn protected: enkel Vlieg en de childs hebben ze nodig, Main niet.

Deel 2: twee soorten drones.

  • LeverDrone erft van Drone en heeft een extra property Gewicht (int): hoeveel kilo boeken hij meeneemt. Een vlucht kost 5 plus 1 per kilo. Met 4 kg boeken is dat 9 procent batterij per vlucht. De beschrijving is levert [Gewicht] kg boeken.
  • RaceDrone erft van Drone en heeft een extra property Topsnelheid (int). Een vlucht kost 5 meer dan bij een gewone drone, dus 10 procent batterij. De beschrijving is racet aan [Topsnelheid] km/u.

Beide childs krijgen een constructor met het model en hun eigen extra waarde. Gebruik in BerekenVerbruik de versie van de parent, en zet de waarschuwing niet in de childs: die staat al in Vlieg.

Deel 3: de vloot. Maak in Main een List<Drone> met een gewone drone Verkenner, een leverdrone Boekenwurm met 4 kg boeken en een racedrone Bliksem die 120 km/u haalt. Laat de vloot ronde na ronde vliegen, zolang er nog minstens één drone batterij heeft. Een drone met 0% vliegt niet meer. Schrijf die stopvoorwaarde zelf met een bool, zonder LINQ.

Het begin van de uitvoer:

--- Ronde 1 ---
Verkenner vliegt rond. Batterij: 95%
Boekenwurm levert 4 kg boeken. Batterij: 91%
Bliksem racet aan 120 km/u. Batterij: 90%
--- Ronde 2 ---
Verkenner vliegt rond. Batterij: 90%
Boekenwurm levert 4 kg boeken. Batterij: 82%
Bliksem racet aan 120 km/u. Batterij: 80%

Ronde 10 tot en met 13:

--- Ronde 10 ---
Verkenner vliegt rond. Batterij: 50%
Boekenwurm levert 4 kg boeken. Batterij: 10%
Waarschuwing: batterij bijna leeg!
Bliksem racet aan 120 km/u. Batterij: 0%
Waarschuwing: batterij bijna leeg!
--- Ronde 11 ---
Verkenner vliegt rond. Batterij: 45%
Boekenwurm levert 4 kg boeken. Batterij: 1%
Waarschuwing: batterij bijna leeg!
--- Ronde 12 ---
Verkenner vliegt rond. Batterij: 40%
Boekenwurm levert 4 kg boeken. Batterij: 0%
Waarschuwing: batterij bijna leeg!
--- Ronde 13 ---
Verkenner vliegt rond. Batterij: 35%

Na ronde 20 staat ook Verkenner op 0% en stopt het programma.

Wil je het als echte vlucht zien? Zet System.Threading.Thread.Sleep(500); aan het einde van elke ronde.

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 gewone methode in de parent kan virtual methoden oproepen die elke child op haar manier invult. Zo staat de gemeenschappelijke code, hier het aftrekken en de waarschuwing, maar één keer in je programma.
  • protected werkt ook voor methoden: de childs kunnen ze overschrijven, Main kan ze niet oproepen.
  • Eén List<Drone> volstaat voor alle soorten drones.

Ballspel met overerving

Tip

Deze oefening is een bonus. Ze bouwt verder op het Pong-voorbeeld uit hoofdstuk 9 en op Pong overerving in het boek.

Je kent het Balletje uit het boek. Hier krijgt het een constructor, en zijn properties hebben een protected set: Main kan ze enkel lezen, child-klassen kunnen ze ook aanpassen. Update en TekenOpScherm zijn virtual. Kopieer de klasse naar een nieuw project:

internal class Balletje
{
    public int X { get; protected set; }
    public int Y { get; protected set; }
    public int VX { get; protected set; }
    public int VY { get; protected set; }

    public Balletje(int x, int y, int vx, int vy)
    {
        X = x;
        Y = y;
        VX = vx;
        VY = vy;
    }

    public virtual void Update()
    {
        if (X + VX >= Console.WindowWidth || X + VX < 0)
        {
            VX = -VX;
        }
        X = X + VX;

        if (Y + VY >= Console.WindowHeight || Y + VY < 0)
        {
            VY = -VY;
        }
        Y = Y + VY;
    }

    public virtual void TekenOpScherm()
    {
        Console.SetCursorPosition(X, Y);
        Console.Write("O");
    }

    public static bool Raakt(Balletje bal1, Balletje bal2)
    {
        return bal1.X == bal2.X && bal1.Y == bal2.Y;
    }
}

En dit spelletje: de speler moet met de pijltjestoetsen het balletje raken.

static void Main(string[] args)
{
    Console.CursorVisible = false;
    Balletje bal = new Balletje(4, 4, 1, 1);
    SpelerBalletje speler = new SpelerBalletje(10, 10);
    bool gewonnen = false;
    while (!gewonnen)
    {
        Console.Clear();
        bal.Update();
        bal.TekenOpScherm();

        if (Console.KeyAvailable)
        {
            speler.VeranderRichting(Console.ReadKey(true).Key);
        }
        speler.Update();
        speler.TekenOpScherm();

        if (Balletje.Raakt(bal, speler))
        {
            gewonnen = true;
        }
        System.Threading.Thread.Sleep(100);
    }
    Console.Clear();
    Console.WriteLine("Gewonnen!");
}

Console.KeyAvailable is true als de gebruiker een toets heeft ingedrukt, en Console.ReadKey(true).Key geeft die toets terug zonder ze op het scherm te zetten. Dat stuk hoef je niet zelf te schrijven.

Deel 1. Schrijf de klasse SpelerBalletje, die erft van Balletje:

  • een constructor met enkel x en y. Het balletje van de speler staat bij de start stil;
  • een methode VeranderRichting(ConsoleKey toets): pijltje omhoog maakt VY 1 kleiner, omlaag 1 groter, links maakt VX 1 kleiner, rechts 1 groter. Gebruik een switch met case ConsoleKey.UpArrow: enzovoort;
  • een override van TekenOpScherm die een groene X tekent in plaats van een O.

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. Schrijf een klasse CentreerBalletje: een balletje dat naar het midden van het scherm springt als het de linker- of rechterkant raakt, zoals in het boek. Gebruik base.Update() voor de rest van de beweging. Vervang in Main het gewone balletje door Balletje bal = new CentreerBalletje(4, 4, 1, 1);. De rest van Main blijft hetzelfde.

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.

Uitbreidingen. Probeer gerust:

  • een List<Balletje> met meerdere balletjes, gewone en centreerballetjes door elkaar, die je met één foreach laat bewegen;
  • een score: tel de rondes tot de speler raakt;
  • levels: na elke treffer een balletje meer;
  • PRO: balletjes die tegen elkaar botsen.
  • Met protected set kan Main de positie enkel lezen, maar mogen de childs ze aanpassen.
  • Een child overschrijft enkel wat anders is. SpelerBalletje beweegt zoals elk balletje en tekent zich anders, CentreerBalletje tekent zich zoals elk balletje en beweegt anders.