• Controleer op null vóór je het eerste puntje achter een object zet.
  • Zet een specifieke catch vóór catch (Exception e). Een leeg catch-blok is geen oplossing: toon minstens wat er misliep, bv. met e.Message (uitleg).
  • Een methode in Program.cs blijft static, ook als ze een object krijgt of teruggeeft.
  • Groene kronkels zoals CS8600, CS8603 of CS8604 bij null mag je laten staan (uitleg).

Kleur mixer (Essential)

Doel: Meng twee kleuren door het gemiddelde te nemen van hun RGB-waarden.

Potloodtekening: de robot giet rode en witte verf samen in een emmer, het stokmannetje vergelijkt met een kleurstaal.

Deel 1. Een kleur die verandert.

  • Klassenaam: Kleur
  • Properties: Rood, Groen en Blauw, alle drie auto-properties van het type int.
  • Methode: MengKleur(Kleur andereKleur) (void).

Wanneer MengKleur wordt aangeroepen, veranderen de properties van de huidige kleur (het object waarop je de methode aanroept). De kleur die je als parameter meegeeft, verandert niet. Gebruik deze formules (gehele deling):

  • Nieuw rood = (huidig rood + ander rood) / 2
  • Nieuw groen = (huidig groen + ander groen) / 2
  • Nieuw blauw = (huidig blauw + ander blauw) / 2

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

Kleur k1 = new Kleur();
k1.Rood = 10;
k1.Groen = 0;
k1.Blauw = 20;

Kleur k2 = new Kleur();
k2.Rood = 10;
k2.Groen = 10;
k2.Blauw = 50;

k1.MengKleur(k2);
Console.WriteLine($"{k1.Rood},{k1.Groen},{k1.Blauw}");
10,5,35

Voeg daarna een lijn toe die ook k2 toont. Welke getallen verwacht je, nog voor je het uitvoert?

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. Een nieuwe kleur.

Soms wil je beide kleuren houden. Voeg een methode Kleur Meng(Kleur andereKleur) toe. Ze rekent met dezelfde formules, maar verandert geen van beide kleuren: ze maakt een nieuwe Kleur en geeft die terug.

Testcode deel 2.

Kleur geel = new Kleur();
geel.Rood = 255;
geel.Groen = 255;
geel.Blauw = 0;

Kleur blauw = new Kleur();
blauw.Blauw = 255;

Kleur mix = geel.Meng(blauw);
Console.WriteLine($"Geel: {geel.Rood},{geel.Groen},{geel.Blauw}");
Console.WriteLine($"Blauw: {blauw.Rood},{blauw.Groen},{blauw.Blauw}");
Console.WriteLine($"Mix: {mix.Rood},{mix.Groen},{mix.Blauw}");
Geel: 255,255,0
Blauw: 0,0,255
Mix: 127,127,127

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 methode die een object meekrijgt, krijgt het adres. Ze kan dat object aanpassen, maar het hoeft niet: MengKleur leest andereKleur enkel uit.
  • MengKleur verandert het object waarop je ze aanroept. Meng maakt met new een derde object en geeft het terug; het returntype is dan Kleur.
  • Zie Objecten in methoden aanpassen en Objecten als resultaat.

Voorspel de uitvoer: de geleende fiets (Essential)

Je kotgenoot vraagt of hij je fiets mag lenen. Natuurlijk. Maar wat gebeurt er met jouw fiets terwijl hij weg is? Bekijk deze klasse en dit programma. Typ niets over. Schrijf eerst op papier wat elk van de zes genummerde lijnen toont.

internal class Fiets
{
    public string Eigenaar { get; set; } = "niemand";
    public string Kleur { get; set; } = "zwart";
    public int Kilometers { get; set; }

    public void Rij(int km)
    {
        Kilometers = Kilometers + km;
    }
}
static void Main(string[] args)
{
    Fiets mijnFiets = new Fiets();
    mijnFiets.Eigenaar = "Lotte";
    mijnFiets.Kilometers = 1200;

    Fiets geleend = mijnFiets;
    geleend.Kleur = "groen";
    geleend.Rij(4);
    Console.WriteLine($"1. {mijnFiets.Kleur}, {mijnFiets.Kilometers} km");

    int teller = mijnFiets.Kilometers;
    teller = teller + 100;
    Console.WriteLine($"2. {mijnFiets.Kilometers} km, teller {teller}");

    VoegRitToe(teller);
    Console.WriteLine($"3. teller {teller}");

    BrengNaarCampus(mijnFiets);
    Console.WriteLine($"4. {mijnFiets.Kilometers} km");

    mijnFiets.Eigenaar.ToUpper();
    Console.WriteLine($"5. {mijnFiets.Eigenaar}");

    Fiets oudeFiets = new Fiets();
    oudeFiets.Eigenaar = "Jonas";
    oudeFiets = geleend;
    Console.WriteLine($"6. {oudeFiets.Eigenaar}, {oudeFiets.Kleur}");
}

static void VoegRitToe(int km)
{
    km = km + 10;
}

static void BrengNaarCampus(Fiets fiets)
{
    fiets.Rij(5);
}

Beantwoord daarna:

  1. Teken de stack en de heap na de laatste lijn van Main. Hoeveel Fiets-objecten staan er in de heap, en welke variabelen wijzen naar welk object?
  2. Welk object mag de Garbage Collector opruimen?

Voer het programma pas daarna uit en vergelijk met je voorspelling.

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 een object kopieert de referentie, bij een int de waarde. Zie = operator bij value types en = operator bij objecten.
  • Een methode met een int-parameter werkt op een kopie, een methode met een object als parameter op het origineel.
  • Een object waar geen enkele variabele meer naar wijst, is onbereikbaar en wordt door de Garbage Collector opgeruimd.
  • ToUpper() verandert een string niet. Zie Wat met string?.

Pokémon (Essential)

We schrijven een klasse die de stats van een Pokémon berekent terwijl hij levelt. Deze klasse komt terug in de volgende oefeningen en in hoofdstuk 11.

Hoe Pokémon werken. Elke Pokémon heeft zes base-stats. Die zijn voor elke soort anders en veranderen nooit: je kan ze als het dna van een Pokémon zien. De full-stats bepalen hoe sterk hij is in een gevecht. Ze worden berekend uit de base-stats en het huidige level: hoe hoger het level, hoe hoger de full-stats.

Schema van de klasse Pokemon: links de public properties (base-stats, auto-properties, Level, read-only properties), rechts de private instantievariabelen. De read-only properties worden in de get berekend uit de base stats en het level.

Maak een klasse Pokemon (in code zonder é) met:

Base-stats. Zes full properties van het type int: HP_Base, Attack_Base, Defense_Base, SpecialAttack_Base, SpecialDefense_Base en Speed_Base. Een base-stat ligt altijd tussen 1 en 255. Krijgt de set een waarde daarbuiten, dan werpt hij een Exception op met als boodschap de naam van de property, gevolgd door moet tussen 1 en 255 liggen. (bv. HP_Base moet tussen 1 en 255 liggen.). De instantievariabele verandert dan niet.

Extra stats. Drie auto-properties: Naam (string), Type (string, bv. grass & poison) en Nummer (int, het nummer in de Pokédex: Bulbasaur heeft 1, Pikachu 25).

Level. Een full property Level (int) met een public get en een private set. Een nieuwe Pokémon start op level 0. De methode VerhoogLevel() verhoogt het level met 1, zonder parameters en zonder returnwaarde.

Statistieken. Twee read-only properties:

  • Total (int): de som van de zes base-stats.
  • Average (double): het gemiddelde van de zes base-stats, dus Total / 6.0.

Full-stats. Zes read-only properties van het type int, die telkens rekenen in hun get:

  • HP_Full = (((HP_Base + 50) * Level) / 50) + 10
  • Attack_Full, Defense_Full, SpecialAttack_Full, SpecialDefense_Full en Speed_Full = ((stat_Base * Level) / 50) + 5. Bijvoorbeeld Attack_Full = ((Attack_Base * Level) / 50) + 5.

De statistieken en de full-stats bewaren niets: ze hebben geen eigen instantievariabele. Omdat de formules met int rekenen, stijgen de full-stats niet bij elk level, maar pas om de paar levels.

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

Pokemon pikachu = new Pokemon();
pikachu.Naam = "Pikachu";
pikachu.Nummer = 25;
pikachu.Type = "electric";
pikachu.HP_Base = 35;
pikachu.Attack_Base = 55;
pikachu.Defense_Base = 40;
pikachu.SpecialAttack_Base = 50;
pikachu.SpecialDefense_Base = 50;
pikachu.Speed_Base = 90;

Console.WriteLine($"{pikachu.Naam} (#{pikachu.Nummer}, {pikachu.Type}) start op level {pikachu.Level}");
Console.WriteLine($"Total {pikachu.Total}, average {pikachu.Average:F2}");
Console.WriteLine($"HP {pikachu.HP_Full}, attack {pikachu.Attack_Full}, speed {pikachu.Speed_Full}");

for (int i = 0; i < 50; i++)
{
    pikachu.VerhoogLevel();
}
Console.WriteLine($"Level {pikachu.Level}: HP {pikachu.HP_Full}, attack {pikachu.Attack_Full}, defense {pikachu.Defense_Full}");
Console.WriteLine($"sp. attack {pikachu.SpecialAttack_Full}, sp. defense {pikachu.SpecialDefense_Full}, speed {pikachu.Speed_Full}");
Console.WriteLine($"Total {pikachu.Total}, average {pikachu.Average:F2}");

try
{
    pikachu.Speed_Base = 300;
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
Console.WriteLine($"Speed_Base is nog altijd {pikachu.Speed_Base}");
Pikachu (#25, electric) start op level 0
Total 320, average 53,33
HP 10, attack 5, speed 5
Level 50: HP 95, attack 60, defense 45
sp. attack 55, sp. defense 55, speed 95
Total 320, average 53,33
Speed_Base moet tussen 1 en 255 liggen.
Speed_Base is nog altijd 90

Maak daarna zelf nog een paar Pokémon met de base-stats uit deze lijst. Je stelt enkel de base-stats in: al de rest volgt daaruit en uit het level.

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.

  • Bewaar enkel wat je niet kan berekenen. De zes base-stats en het level zijn genoeg; Total, Average en de full-stats rekenen telkens opnieuw in hun get.
  • De set werpt een exception op en zegt zo aan wie hem oproept dat er iets misliep. Wat er dan moet gebeuren, beslist de code die de property gebruikt, met een catch. Zie Een exception genereren met throw.
  • Level heeft een private set: enkel VerhoogLevel kan het level veranderen.

Stevens reservekopie (Essential)

Stagiair Steven werkt met de Pokémon-klasse uit de vorige oefening. Hier is ze ingekort: enkel de stats die hij nodig heeft, met auto-properties voor de base-stats. Level en de formules van HP_Full en Attack_Full zijn dezelfde als in de opgave.

internal class Pokemon
{
    public string Naam { get; set; } = "Onbekend";
    public int HP_Base { get; set; }
    public int Attack_Base { get; set; }

    private int level;
    public int Level
    {
        get { return level; }
        private set { level = value; }
    }

    public void VerhoogLevel()
    {
        Level++;
    }

    public int HP_Full
    {
        get { return (((HP_Base + 50) * Level) / 50) + 10; }
    }

    public int Attack_Full
    {
        get { return ((Attack_Base * Level) / 50) + 5; }
    }
}

Steven wil tonen wat tien levels training met zijn Pikachu doen. Hij vroeg een A.I. om eerst een reservekopie te nemen, zodat hij voor en na kan vergelijken, en om Pikachu daarna tegen een rivaal te laten vechten. MaakRivaal kent voorlopig enkel Onix. Steven test bewust met een rivaal die zijn programma niet kent, om te zien of zijn foutmelding werkt. Dit zette de A.I. in Program.cs:

static void Main(string[] args)
{
    Pokemon pikachu = new Pokemon();
    pikachu.Naam = "Pikachu";
    pikachu.HP_Base = 35;
    pikachu.Attack_Base = 55;

    Pokemon reserve = pikachu; // reservekopie van voor de training

    for (int i = 0; i < 10; i++)
    {
        pikachu.VerhoogLevel();
    }

    Console.WriteLine($"Voor de training: level {reserve.Level}, HP {reserve.HP_Full}");
    Console.WriteLine($"Na de training: level {pikachu.Level}, HP {pikachu.HP_Full}");

    Pokemon rivaal = MaakRivaal("Mew");
    try
    {
        int winnaar = Battle(pikachu, rivaal);
        Console.WriteLine($"Winnaar: Pokémon {winnaar}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"Geen gevecht: {e.Message}");
    }
}

static Pokemon MaakRivaal(string naam)
{
    Pokemon rivaal = null;
    if (naam == "Onix")
    {
        rivaal = new Pokemon();
        rivaal.Naam = "Onix";
        rivaal.HP_Base = 35;
        rivaal.Attack_Base = 45;
    }
    return rivaal;
}

static int Battle(Pokemon poke1, Pokemon poke2)
{
    int kracht1 = poke1.HP_Full + poke1.Attack_Full;
    int kracht2 = poke2.HP_Full + poke2.Attack_Full;

    if (poke1 == null || poke2 == null)
    {
        throw new Exception("Een gevecht heeft twee Pokémon nodig.");
    }

    int winnaar = 0;
    if (kracht1 > kracht2)
    {
        winnaar = 1;
    }
    else if (kracht2 > kracht1)
    {
        winnaar = 2;
    }
    return winnaar;
}

Deel 1. Het compileert zonder foutmeldingen en het crasht niet. Een nieuwe Pokémon start op level 0, dus Steven verwacht als eerste lijn Voor de training: level 0, HP 10. Voer het uit. Wat krijgt hij, en waarom? Pas het stuk rond reserve aan, zodat reserve echt de Pikachu van voor de training bewaart.

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. Kijk naar de laatste lijn van de uitvoer. Is dat de melding die Steven in Battle schreef? Zoek waar ze vandaan komt, en pas Battle aan zodat Steven zijn eigen melding te zien krijgt.

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 vindt geen van beide fouten, en het programma crasht niet. Enkel de uitvoer verraadt dat er iets mis is.
  • = bij een object kopieert de referentie, niet het object. Zie = operator bij objecten.
  • Controleer op null vóór je het eerste puntje achter het object zet. Zie NullReferenceException voorkomen.
  • catch (Exception e) vangt ook uitzonderingen op die je niet verwacht. Hier toonde e.Message dat het niet om Stevens eigen exception ging. Zie Werken met de exception parameter.

Kies je starter (Essential)

Elke Pokémontrainer begint met één van drie starters. Schrijf in Program.cs een methode static Pokemon KiesStarter(string naam) die je Pokemon-klasse gebruikt:

Naam Nummer Type HP Attack Defense Sp. attack Sp. defense Speed
Bulbasaur 1 grass & poison 45 49 49 65 65 45
Charmander 4 fire 39 52 43 60 50 65
Squirtle 7 water 44 48 65 50 64 43

Is naam een van die drie, dan maakt de methode die Pokémon aan, vult alles in en geeft hem terug. Voor elke andere naam geeft ze null terug.

Deel 1. Zet deze code in je Main:

Pokemon eerste = KiesStarter("Charmander");
Console.WriteLine($"Je koos {eerste?.Naam}, total {eerste?.Total}");

Pokemon tweede = KiesStarter("Pikachu");
Console.WriteLine($"Je koos {tweede?.Naam}, total {tweede?.Total}");

Pokemon derde = KiesStarter("squirtle");
Console.WriteLine($"Je koos {derde?.Naam}, total {derde?.Total}");
Je koos Charmander, total 309
Je koos , total 
Je koos , total 

Waarom zijn de laatste twee lijnen leeg? Haal daarna bij tweede de twee vraagtekens weg. Wat gebeurt er 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 2. Vraag de gebruiker welke starter hij wil, tot hij een naam typt die KiesStarter kent. Toon daarna zijn keuze.

Kies je starter: Bulbasaur, Charmander of Squirtle
>Pikachu
Die starter ken ik niet.
Kies je starter: Bulbasaur, Charmander of Squirtle
>charmander
Die starter ken ik niet.
Kies je starter: Bulbasaur, Charmander of Squirtle
>Charmander
Charmander (#4, fire) is je eerste Pokémon!

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 methode mag een object teruggeven, en null als er niets te geven is. Wie de methode oproept, moet dat dan wel controleren. Zie Return null.
  • object?.Naam is de verkorte null-controle: geen crash, maar ook geen naam. Zie Verkorte null controle notatie.
  • null kan je ook als voorwaarde in een lus gebruiken: while (gekozen == null).

Pokémontester (Essential)

Deel 1: ShowInfo. Het is een heel gedoe om telkens alle stats van een Pokémon apart op het scherm te zetten. Voeg aan je Pokemon-klasse een methode public void ShowInfo() toe die alles toont, in deze vorm:

Pikachu (#25, electric), level 50
Base stats: HP 35, attack 55, defense 40, sp. attack 50, sp. defense 50, speed 90
Full stats: HP 95, attack 60, defense 45, sp. attack 55, sp. defense 55, speed 95
Total 320, average 53,33

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: een nieuw project.

  1. Maak een nieuwe console-applicatie “PokemonTester”.
  2. Voeg het bestand van je Pokemon-klasse toe: rechtsklik op het project, Add > Existing Item… en kies Pokemon.cs uit je vorige project.
  3. Schrijf in Main Pokemon test = new Pokemon();. Er verschijnt een rode kronkel onder Pokemon. Waarom? Los het op via het lampje. Welke twee opties biedt Visual Studio je?
  4. Draai je keuze terug en pas nu de namespace bovenaan Pokemon.cs aan naar die van het nieuwe project. Wat gebeurt er met de kronkel?

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: invoer die niet crasht. Schrijf een programma dat de gebruiker de naam en de zes base-stats van een Pokémon vraagt, de Pokémon toont met ShowInfo, vraagt hoeveel keer hij moet levelen, en hem daarna opnieuw toont.

Een gebruiker typt niet altijd wat je verwacht. Letters, een kommagetal, een getal van twaalf cijfers of een stat van 400 mogen je programma niet laten crashen.

  • Zoek eerst in de documentatie van Convert.ToInt32(String) welke exceptions die methode kan opwerpen, en wanneer.
  • Versie 1: zet alles in één try. Vang de twee exceptions uit de documentatie elk apart op, met een eigen melding, en zet als laatste catch (Exception e), die e.Message toont. Die vangt de exception op die je set opwerpt bij een stat buiten 1-255.
Naam?
>Pikachu
HP?
>35
Attack?
>vijftig
Dat is geen geheel getal.
  • Versie 2: bij een foute stat begint de gebruiker in versie 1 helemaal opnieuw. Schrijf een methode static void VraagStat(Pokemon poke, string stat) die één stat vraagt en blijft vragen tot de waarde geldig is: een lus met een bool, en de try in de lus. Gebruik geen break om uit de lus te springen. Roep ze op voor de zes stats. Is het aantal levels geen geldig getal, dan blijft de Pokémon op level 0.
Naam?
>Pikachu
HP?
>35
Attack?
>vijftig
Dat is geen geheel getal.
Attack?
>55
Defense?
>400
Defense_Base moet tussen 1 en 255 liggen.
Defense?
>40
SpecialAttack?
>99999999999
Dat getal is veel te groot.
SpecialAttack?
>50
SpecialDefense?
>50
Speed?
>90
Pikachu (#0, onbekend), level 0
Base stats: HP 35, attack 55, defense 40, sp. attack 50, sp. defense 50, speed 90
Full stats: HP 10, attack 5, defense 5, sp. attack 5, sp. defense 5, speed 5
Total 320, average 53,33
Hoeveel keer moet je Pokémon levelen?
>50
Pikachu (#0, onbekend), level 50
Base stats: HP 35, attack 55, defense 40, sp. attack 50, sp. defense 50, speed 90
Full stats: HP 95, attack 60, defense 45, sp. attack 55, sp. defense 55, speed 95
Total 320, average 53,33

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.

Pokémon-battler (Essential)

Tijd om te vechten. Beide methoden hieronder staan in Program.cs, niet in de klasse: ze horen niet bij één Pokémon.

Pokémon-generator. Schrijf static Pokemon GeneratorPokemon(string naam, Random generator). De methode maakt een Pokémon met die naam, zes willekeurige base-stats tussen 20 en 150, en een willekeurig level tussen 1 en 50 (roep VerhoogLevel zoveel keer op). Ze geeft de Pokémon terug. Maak in Main één Random en geef die telkens mee.

Battle. Schrijf static int Battle(Pokemon poke1, Pokemon poke2). Ze geeft 1 terug als poke1 wint, 2 als poke2 wint en 0 bij gelijkspel. Wie wint, bepaal je zelf, maar gebruik de full-stats: dan speelt het level mee. (Gewoon de levels vergelijken telt niet.) Is poke1 of poke2 null, dan werpt Battle een Exception op met de boodschap Battle kan niet doorgaan: een van beide Pokémon is null.

In Main.

  1. Genereer twee wilde Pokémon, toon ze met ShowInfo en laat ze vechten. Toon wie wint.
  2. Roep daarna Battle(links, niemand) op, met Pokemon niemand = null;. Doe dat eerst zonder try. Wat zie je?
  3. Zet die oproep nu in een try en toon in de catch e.Message.
Wilde Rattata (#0, onbekend), level 27
Base stats: HP 25, attack 85, defense 66, sp. attack 107, sp. defense 79, speed 144
Full stats: HP 50, attack 50, defense 40, sp. attack 62, sp. defense 47, speed 82
Total 506, average 84,33
Wilde Pidgey (#0, onbekend), level 8
Base stats: HP 72, attack 95, defense 40, sp. attack 47, sp. defense 62, speed 90
Full stats: HP 29, attack 20, defense 11, sp. attack 12, sp. defense 14, speed 19
Total 406, average 67,67
Wilde Rattata wint!
Geen gevecht: Battle kan niet doorgaan: een van beide Pokémon is null.

Bij jou komen er andere getallen uit.

Uitbreiding. Zet de gevechten in een lus: de winnaar levelt na elk gevecht, de verliezer wordt vervangen door een nieuwe wilde Pokémon. Hoe lang houdt je kampioen het vol?

Bonus: hoe snel is je battle? Hoe lang duurt het om een miljoen gevechten uit te rekenen? Daarvoor bestaat de klasse Stopwatch: Start() zet ze aan, Stop() zet ze stil, en de property ElapsedMilliseconds geeft het aantal milliseconden daartussen. Typ Stopwatch klok = new Stopwatch();. Waarom kent C# die klasse niet, en hoe los je dat op?

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.

  • Battle controleert zelf wat het binnenkrijgt en werpt een exception op. Zonder catch stopt je programma met die boodschap; met een catch beslist Main wat er gebeurt.
  • GeneratorPokemon maakt een object aan en geeft het terug. Main heeft daarna een referentie naar een Pokémon die in de methode gemaakt werd.
  • Eén Random voor het hele programma: maak hem in Main en geef hem mee.
  • Een klasse uit .NET die C# niet vindt, staat meestal in een namespace die je nog niet geladen hebt. Zie using in je code.

Voorspel de uitvoer: printen in de bib (Essential)

De avond voor het examen print je in de bib nog snel drie bestanden. De printer neemt enkel pdf’s aan. Dit is het programma, met een lege plek in Main:

static void Main(string[] args)
{
    string[] bestanden = { "samenvatting.pdf", "notities.docx", "oefeningen.pdf" };

    // hier komt versie 1, 2 of 3

    Console.WriteLine("Op naar huis.");
}

static void Print(string bestand)
{
    if (!bestand.EndsWith(".pdf"))
    {
        throw new Exception($"{bestand} is geen pdf.");
    }
    Console.WriteLine($"{bestand} geprint.");
}

Op de lege plek komt telkens een van deze drie versies:

// Versie 1
for (int i = 0; i < bestanden.Length; i++)
{
    Print(bestanden[i]);
}
// Versie 2
try
{
    for (int i = 0; i < bestanden.Length; i++)
    {
        Print(bestanden[i]);
    }
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
finally
{
    Console.WriteLine("Printer vrijgegeven.");
}
// Versie 3
for (int i = 0; i < bestanden.Length; i++)
{
    try
    {
        Print(bestanden[i]);
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    finally
    {
        Console.WriteLine("Printer vrijgegeven.");
    }
}

En dit zijn de drie uitvoeren, door elkaar:

Uitvoer A

samenvatting.pdf geprint.
Printer vrijgegeven.
notities.docx is geen pdf.
Printer vrijgegeven.
oefeningen.pdf geprint.
Printer vrijgegeven.
Op naar huis.

Uitvoer B

samenvatting.pdf geprint.
Unhandled exception. System.Exception: notities.docx is geen pdf.
   at ...

Uitvoer C

samenvatting.pdf geprint.
notities.docx is geen pdf.
Printer vrijgegeven.
Op naar huis.
  1. Welke uitvoer hoort bij welke versie? Beslis eerst op papier, en probeer het daarna uit.
  2. Waarom wordt oefeningen.pdf in uitvoer C niet geprint?
  3. Welke versie kies je als je zoveel mogelijk wil printen?

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 throw springt naar de eerste catch die klaarstaat, ook als die in Main staat en niet in de methode zelf.
  • finally loopt altijd, met of zonder exception. Zie finally: het derde lid.
  • Een try rond de lus stopt bij de eerste fout, een try in de lus slaat enkel dat ene element over. Zie Rond afzonderlijke elementen in de loop.

Bankmanager 2: terugbetalen na de citytrip

Lotte en Mo zijn net terug van een citytrip naar Berlijn. Ze hebben om beurt voor elkaar betaald en betalen elkaar nu terug, elk vanaf een nieuwe studentenrekening. Je werkt verder met de klasse Rekening uit BankManager (deel 2, met de Staat). Beide nieuwe methoden staan in Program.cs.

Deel 1. Schrijf static Rekening CreeerStudentenRekening(string klantNaam, string rekeningNummer). Ze maakt een nieuwe rekening, vult naam en nummer in en geeft ze terug. Een studentenrekening start met 50 euro. Balans heeft een private set, dus die 50 euro komt er via StortGeld.

Testcode deel 1. Zet deze code in je Main.

Rekening lotte = CreeerStudentenRekening("Lotte", "BE02 2000");
Rekening mo = CreeerStudentenRekening("Mo", "BE03 3000");
lotte.ToonInfo();
mo.ToonInfo();

lotte.StortGeld(mo.HaalGeldAf(20));
lotte.ToonInfo();
mo.ToonInfo();
Lotte (BE02 2000): 50 euro, Geldig
Mo (BE03 3000): 50 euro, Geldig
Lotte (BE02 2000): 70 euro, Geldig
Mo (BE03 3000): 30 euro, Geldig

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 static void SimuleerOverdracht(Rekening r1, Rekening r2, Random generator). Vijf keer na elkaar:

  • kies een willekeurig bedrag van 1 tot en met 100 euro;
  • toon wie hoeveel terugbetaalt aan wie;
  • haal het bedrag af bij de verzender en stort wat er afgehaald werd bij de ontvanger;
  • wissel daarna de rollen om: eerst betaalt r1 aan r2, dan r2 aan r1, enzovoort.

Main toont hoeveel geld ze samen hebben, laat de overdrachten gebeuren, toont beide rekeningen en daarna opnieuw het totaal. Voer het programma een paar keer uit. Bijvoorbeeld:

Samen voor de citytrip: 100 euro
Lotte betaalt 25 euro terug aan Mo.
Mo betaalt 12 euro terug aan Lotte.
Lotte betaalt 47 euro terug aan Mo.
Rekening leeg nu
Mo betaalt 78 euro terug aan Lotte.
Gaat niet. Rekening geblokkeerd.
Lotte betaalt 66 euro terug aan Mo.
Gaat niet. Rekening geblokkeerd.
Ongeldig bedrag.
Lotte (BE02 2000): 0 euro, Geblokkeerd
Mo (BE03 3000): 22 euro, Geldig
Samen na de citytrip: 22 euro

Ze betalen elkaar enkel terug, dus samen hebben ze na afloop nog altijd 100 euro. Toch? Zoek uit waar het geld naartoe gaat, en waar Ongeldig bedrag. vandaan komt. Pas SimuleerOverdracht dan aan zodat er geen geld meer verdwijnt.

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.

  • SimuleerOverdracht krijgt de adressen van de twee rekeningen. Wat ze eraan verandert, zie je achteraf in Main.
  • Rekening verzender = r1; kopieert enkel een referentie. Zo wissel je de rollen om zonder een rekening te kopiëren.
  • Elke methode van Rekening werkte correct, en toch verdween er geld: StortGeld(HaalGeldAf(...)) haalt eerst af en controleert pas daarna of de ontvanger iets mag krijgen. Een controle op het totaal vindt zo’n fout.

Project: SpaceCommand (Final Essentials)

Tip

Een Final Essentials oefening is een opgave waarin alle leerstof van de voorbije oefeningen aan bod komt.

Je bent de software-architect van de Aardse Vloot. Je schrijft een systeem om ruimteschepen te beheren, op te waarderen en tegen elkaar te laten oefenen. Objecten doorgeven en teruggeven, referenties, null en exceptions: alles van dit hoofdstuk komt terug.

Deel 1: de klasse RuimteSchip

Property of methode Type Werking
Naam string Auto-property.
Kapitein string Auto-property met private set, beginwaarde "Onbekend".
WisselKapitein(string nieuweKapitein) void Enige manier om de kapitein te veranderen.
RompSterkte, SchildKracht, VuurKracht int Auto-properties, van 0 tot 100.
Ervaring int Auto-property met private set, start op 0.
Schade int Auto-property met private set, start op 0.
IsKapot bool Read-only: true als Schade groter dan of gelijk aan RompSterkte is.
TotaleKracht double Read-only: VuurKracht * Ervaring + SchildKracht * 0.5.
TrainBemanning() void Verhoogt Ervaring met 1.
LoopSchadeOp(int punten) void Verhoogt Schade met punten.
Onderhoud() void Zet Schade op 0 en toont Schip [Naam] is volledig hersteld.
ToonRapport() void Toont naam, kapitein, stats, ervaring, schade, kracht en of het schip kapot is (zie het voorbeeld).

Deel 2: de vlootmanager (Program.cs)

  1. static RuimteSchip MaakWillekeurigSchip(string naam, Random generator) maakt een schip met die naam en geeft het terug. RompSterkte, SchildKracht en VuurKracht krijgen elk een willekeurige waarde van 10 tot en met 100.
  2. static RuimteSchip SimuleerGevecht(RuimteSchip s1, RuimteSchip s2):
    • Is een van beide schepen null, dan werpt de methode een Exception op: Een gevecht heeft twee schepen nodig.
    • Is een van beide schepen kapot, dan werpt ze een Exception op: [Naam] is kapot en moet eerst in onderhoud.
    • Het schip met de laagste TotaleKracht verliest. Toon wie wint, met de kracht van beide schepen. Daarna loopt de verliezer 20 schade op en traint de winnaar zijn bemanning.
    • De methode geeft de verliezer terug. Zijn beide even sterk, dan is het gelijkspel: toon dat, er verandert niets, en de methode geeft null terug.
  3. static void PimpMijnSchip(RuimteSchip schip) zet VuurKracht en SchildKracht van het schip op 100.

Deel 3: het scenario (Main)

  1. Maak met één Random twee schepen: “De Voyager” en “De Millennium Falcon”.
  2. Geef ze een kapitein: Janeway en Solo.
  3. Toon beide rapporten.
  4. Laat ze vechten en toon de rapporten opnieuw. Zie je de schade en de ervaring?
  5. Was er een verliezer, stuur die dan naar de garage (PimpMijnSchip), toon zijn rapport en laat ze nog eens vechten. Wint de verliezer nu? Waarom (niet)?
  6. Schrijf RuimteSchip s3 = s1; en geef s3 een andere naam, bv. “De Enterprise”. Toon het rapport van s1. Wat merk je? Teken de stack en de heap.
  7. Roep SimuleerGevecht(s1, null) op in een try en toon e.Message.
  8. Laat ze nog vijf keer vechten, in een lus. Wordt een schip kapot, dan moet de lus gewoon verder kunnen en toont ze de melding. Waar zet je de try?
  9. Stuur na de lus beide schepen in onderhoud en toon hun rapport.

Bijvoorbeeld (bij jou komen er andere getallen uit):

De Voyager (kapitein Janeway): romp 84, schild 100, vuur 61
   ervaring 0, schade 0, kracht 50, kapot: False
De Millennium Falcon (kapitein Solo): romp 47, schild 11, vuur 14
   ervaring 0, schade 0, kracht 5,5, kapot: False
De Voyager (50) verslaat De Millennium Falcon (5,5).
De Voyager (kapitein Janeway): romp 84, schild 100, vuur 61
   ervaring 1, schade 0, kracht 111, kapot: False
De Millennium Falcon (kapitein Solo): romp 47, schild 11, vuur 14
   ervaring 0, schade 20, kracht 5,5, kapot: False
De Millennium Falcon (kapitein Solo): romp 47, schild 100, vuur 100
   ervaring 0, schade 20, kracht 50, kapot: False
De Voyager (111) verslaat De Millennium Falcon (50).
De Enterprise (kapitein Janeway): romp 84, schild 100, vuur 61
   ervaring 2, schade 0, kracht 172, kapot: False
Fout: Een gevecht heeft twee schepen nodig.
De Enterprise (172) verslaat De Millennium Falcon (50).
Fout: De Millennium Falcon is kapot en moet eerst in onderhoud.
Fout: De Millennium Falcon is kapot en moet eerst in onderhoud.
Fout: De Millennium Falcon is kapot en moet eerst in onderhoud.
Fout: De Millennium Falcon is kapot en moet eerst in onderhoud.
Schip De Enterprise is volledig hersteld.
Schip De Millennium Falcon is volledig hersteld.
De Enterprise (kapitein Janeway): romp 84, schild 100, vuur 61
   ervaring 3, schade 0, kracht 233, kapot: False
De Millennium Falcon (kapitein Solo): romp 47, schild 100, vuur 100
   ervaring 0, schade 0, kracht 50, kapot: 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.

  • PimpMijnSchip en SimuleerGevecht krijgen het adres van een schip: wat ze veranderen, zie je in Main. MaakWillekeurigSchip en SimuleerGevecht geven een schip terug, SimuleerGevecht ook null bij gelijkspel.
  • s3 = s1 maakt geen tweede schip.
  • SimuleerGevecht weigert met een exception wat niet kan, Main vangt ze op. Zo kan een gevecht nooit stilletjes met een kapot schip doorgaan.
  • De formule bepaalt wie wint, niet je verwachting. Twijfel je, reken het na met de getallen uit het rapport.