arrow-left

All pages
gitbookPowered by GitBook
1 of 5

Loading...

Loading...

Loading...

Loading...

Loading...

H9: Geheugenmanagement bij klassen

Objecten en methoden

hashtag
Objecten als argumenten

Klassen zijn "gewoon" nieuwe types. Alle regels die we dus al kenden in verband met het doorgeven van variabelen als parameters in een methoden blijven gelden. Het enige verschil is dat we objecten by reference meegeven aan een methode. Aanpassingen aan het object in de methode zal dus betekenen dat je het originele object aanpast dat aan de methode werd meegegeven. Hier moet je dus zeker rekening mee houden.

Een voorbeeld. Stel dat we volgende klasse hebben waarin we metingen willen opslaan, alsook wie de meting heeft gedaan:

In ons hoofdprogramma schrijven we een methode ToonMetingInKleur die ons toelaat om deze meting op het scherm te tonen in een bepaalde kleur. Het gebruik en de methode zelf zouden er zo kunnen uitzien:

hashtag
Objecten in methoden aanpassen

Je kan dus ook methoden schrijven die meegegeven objecten aanpassen daar we deze by reference doorsturen. Een voorbeeld:

Als we deze methode als volgt aanroepen

Da zullen we zien dat de temperatuur in m1 effectief met 1 werd verhoogd.

Dit gedrag zouden we NIET zien bij volgende methode daar int by value wordt doorgegeven:

hashtag
Delen van objecten als parameter

Stel dat we volgende methode hebben

Je mag deze methode dus ook oproepen als volgt (we gebruiken de Meting objecten m1 en m2 uit vorige paragraaf):

Het type van de property Temperatuur is int en mag je dus als parameter aan deze methoden meegeven.

hashtag
Objecten als resultaat

Weer hetzelfde verhaal: ook klassen mogen het resultaat van een methoden zijn.

Deze methode kan je als volgt dan gebruiken:

Merk op dat het dus kan zijn dat een methode null teruggeeft. Het kan dus zeker geen kwaad om steeds in je code te controleren of je effectief iets hebt terug gekregen:

class Meting
{
    public int Temperatuur { get; set; }
    public string OpgemetenDoor { get; set; }
}
static void Main(string[] args)
{
    Meting m1 = new Meting();
    m1.Temperatuur = 26; 
    m1.OpgemetenDoor = "Elon Musk";
    Meting m2 = new Meting();
    m2.Temperatuur = 34; 
    m2.OpgemetenDoor = "Dennis Rodman";

    ToonMetingInKleur(m1, ConsoleColor.Red);
    ToonMetingInKleur(m2, ConsoleColor.Gray);
}

static void ToonMetingInKleur (Meting inmeting, ConsoleColor kleur)
{
    Console.ForegroundColor = kleur;
    Console.WriteLine($"Temperatuur {inmeting.Temperatuur}°C werd opgemeten door {inmeting.OpgemetenDoor}");
    Console.ResetColor();
}
static void ToonMetingEnVerhoog(Meting inmeting)
{
    ToonMetingInKleur(inmeting, ConsoleColor.Green);

    inmeting.Temperatuur++;
}
Meting m1 = new Meting();
m1.Temperatuur = 26; m1.OpgemetenDoor = "Elon Musk";

ToonMetingEnVerhoog(m1);

Console.WriteLine(m1.Temperatuur);
static void VerhoogGetal(int inmeting)
{
    inmeting++;
}
static double Gemiddelde(double getal1, double getal2)
{
    return (getal1 + getal2) / 2;
}
double result= Gemiddelde(m1.Temperatuur, m2.Temperatuur);
static Meting GenereerRandomMeting()
{
    Meting result = new Meting();
    Random r = new Random();
    result.Temperatuur = r.Next(-100, 200);
    result.OpgemetenDoor = "Onbekend";

    return result;
}
Meting m3 = GenereerRandomMeting();
Meting m3 = GenereerRandomMeting();
if(m3 != null)
{
    ToonMetingInKleur(m3, ConsoleColor.Red);
}

Null en NullReferenceException

Zoals nu duidelijk is bevatten variabelen steeds een referentie naar een object. Maar wat als we dit schrijven:

Dit zal een fout geven. stud1 bevat namelijk nog geen referentie. Maar wat dan wel?

Deze variabele bevat de waarde null . Net zoals bij value types die een default waarde hebben (bv 0 bij een int ) als je er geen geeft, zo bevat reference types altijd null.

hashtag
NullReferenceException

Een veel voorkomende foutboodschap tijdens de uitvoer van je applicatie is de zogenaamde NullReferenceException . Deze zal optreden wanneer je code een object probeert te benaderen wiens waarde null is.

Laten we dit eens simuleren:

Dit zal resulteren in volgende foutboodschap:

We moeten in dit voorbeeld expliciet =null plaatsen daar Visual Studio slim genoeg is om je te waarschuwen voor eenvoudige potentiele NullReference fouten en je code anders niet zal compileren.

hashtag
NullReferenceException voorkomen

Objecten die niet bestaan zullen altijd null. Uiteraard kan je niet altijd al je code uitvlooien waar je misschien =new SomeObject(); bent vergeten.

Voorts kan het ook soms by design zijn dat een object voorlopig null is.

Gelukkig kan je controleren of een object null is als volgt:

hashtag
Verkorte null controle notatie

Vaak moet je dit soort code schrijven:

Op die manier voorkom je NullReferenceException. Het is uiteraard omslachtig om steeds die check te doen. Je mag daarom ook schrijven:

Het vraagteken direct na het object geeft aan: "de code na dit vraagteken enkel uitvoeren indien het object voor het vraagteken niét null is".

Bovenstaande code zal dus gewoon een lege lijn op scherm plaatsen indien stud1 effetief null is, anders komt de naam op het scherm.

hashtag
Return null

Uiteraard mag je dus ook expliciet soms null teruggeven als resultaat van een methode. Stel dat je een methode hebt die in een array een bepaald object moet zoeken. Wat moet de methode teruggeven als deze niet gevonden wordt? Inderdaad, we geven dan null terug.

Volgende methode zoekt in een array van studenten naar een student met een specifieke naam en geeft deze terug als resultaat. Enkel als de hele array werd doorlopen en er geen match is wordt er null teruggegeven (de werking van arrays van objecten worden later besproken):

Oefeningen

We gaan een programma schrijven dat ons toelaat enkele basis-eigenschappen van specifieke Pokémon te berekenen terwijl ze levellen. Nadruk van deze oefening is het juist gebruiken van properties. Bekijk de cheat sheet bij twijfel.

Disclaimer: de informatie in deze tekst is een vereenvoudigde versie van de echte Pokémon-stats in de mate dat ik het allemaal een beetje kon begrijpen en juist interpreteren.

hashtag
Hoe Pokémon werken

Korte uitleg over Pokémon en hun interne werking: Iedere Pokémon wordt uniek gemaakt door z’n base-stats, deze zijn voor iedere Pokémon anders. Deze base-stats (punt 3) zijn onveranderlijk en blijven dus doorheen het hele leven van een Pokémon dezelfde. Je kan de base-stats als het dna van een Pokémon beschouwen.

De full-stats (punt 9) zijn echter de stats die de effectieve ‘krachten’ van een Pokémon bepalen in een gevecht. Deze stats worden berekend gebaseerd op de vaste base-stats en het huidige level van de Pokémon. Hoe hoger het level van de Pokémon, hoe hoger dus zijn full-stats.

hashtag
De Pokémonopdracht

Maak een consoleapplicatie met daarin een klasse Pokémon die de werking zoals hierboven beschreven heeft:

hashtag
Base-stats

De base-stats worden als ints bewaard. Maak voor al deze basis-eigenschappen full properties, namelijk:

  • HP_Base

  • Attack_Base

  • Defense_Base

hashtag
Extra stats

Voorts wordt een Pokémon ook gedefinieerd door z’n naam (string) ,type (string, bv "grass & poison") en nummer (int), maak hiervoor auto properties aan.

hashtag
Level

Voeg een fullproperty Level toe(type int). Deze heeft een public get, maar een private setter.

Voeg een publieke methode "VerhoogLevel" toe. Deze methode zal , via de private setter van Level (zie vorig punt), de level van de Pokémon met 1 verhogen. Deze methode heeft géén parameters nodig en return niets.

hashtag
Statistieken

Voeg 2 read-only properties toe (enkel get, géén set) genaamd "Average" en "Total":

  • De Average-property geeft het gemiddelde van de 6 base-stats terug , dus (HP_Base + Attack_Base + Defense_Base + SpAttack_Base + SpDefense_Base +Speed_Base)/6.

  • De Total-property geeft de som terug van de 6 basestats. Daar de base stats niet evolueren met het level veranderen dus Average en Total ook niet van zodra de base-stats werden ingesteld, toch mag je beide statistieken steeds herberekenen in de get.

hashtag
Level-gebaseerde stats

De eigenschappen van de Pokémon die mee evolueren met het leven gaan we steeds als read-only property implementeren:

  • Voeg een read-only HP_Full property (int) toe om de maximum health voor te stellen. Deze wordt berekend als volgt: (( (HP_Base + 50 ) * Level ) / 50 ) + 10 (noot: dit is een benadering van hoe het bij "echte" Pokémon is ).

  • Voeg voor iedere base-stat een full-stat toe (int). Dus Defense_Full, Speed_Full, etc. Ook deze properties zijn readonly. Deze stats worden berekend als volgt: ( (stat_Base*Level) / 50 ) + 5. Attack_Full bijvoorbeeld wordt dus berekend als: ((Attack_Base*Level)/50)+5

hashtag
Maak enkele Pokémon

Kies enkele Pokémon uit en maak in je Main enkele Pokémon-objecten aan met de juiste eigenschappen.

Opgelet: Je dient dus enkel de base stats in te stellen. Alle andere zaken zijn op deze stats en het huidige level van de Pokémon gebaseerd.

Toon aan dat de Average, Total , HP en andere stats correct berekend worden (controleer in de tabel op de voorgaande url).

hashtag
Level-up tester

Maak een kleine loop die je toelaat om per loop een bepaalde Pokémon z’n level met 1 te verhogen en vervolgens toon je dan z’n nieuwe stats.

1Test eens hoe de stats na bv 100 levels evolueren. Je zal zien dat bepaalde stats pas na een paar keer levelen ook effectief beginnen stijgen.

Voeg extra functionaliteit naar keuze toe

hashtag
Deel 2: De Pokémontester

vergelijk je oplossing uit het vorige deel .

Het is een heel gedoe om telkens manueel de informatie van een Pokémon op het scherm te outputen. Voeg een methode public void ShowInfo() toe aan je Pokemon klasse. Deze methode zal alle relevante informatie (alle properties!) in een mooie vorm op het scherm tonen, bv:

Maak nu een nieuwe console-applicatie genaamd "Pokémon Tester":

  1. Voeg je Pokemon-klasse-bestand toe aan dit project. Verander de "namespace" van dit bestand naar de namespace van je nieuwe console-applicatie (zie "Aanpassen van klasse" in )

  2. Maak enkele Pokémon objecten aan en stel hun base stats in.

  3. Schrijf een applicatie die aan de gebruiker eerst de 6 base-stats vraagt. Vervolgens wordt de Pokémon aangemaakt met die stats en worden de full-stats aan de gebruiker getoond

hashtag
Deel 3: Pokémon-battler

hashtag
Pokémon generator

Maak een methode met volgende signatuur: static Pokemon GeneratorPokemon(). Plaats deze methode niét in je Pokémon-klasse, maar in Program.cs.

Deze methode zal telkens een Pokémon aanmaken met willekeurige base-stats. Bepaal zelf hoe je dit gaat doen.

hashtag
Battle tester

Voeg een methode met volgende signatuur toe aan je hoofdprogramma (dus ook weer in Program.cs): static int Battle(Pokemon poke1, Pokemon poke2).

De methode zal een getal teruggeven dat aangeeft welke van de twee Pokémons een gevecht zou winnen. 1= poke1, 2 = poke2, 0 = gelijke stand.

Controleer steeds of 1 of beide van de meegegeven Pokémons niet null zijn. Indien er 1 null is dan wint uiteraard de andere. Indien allebei null wint niemand (dus return je 0). Test of dit werkt!

Bepaal zelf hoe Pokémons vechten (bv degene met de hoogste average van full-stats). Werk niet enkel met de base-stats, daar deze constant zijn. Het is leuker dat het level ook een invloed heeft (maar ga niet gewoon het level vergelijken)

hashtag
Alles samen

Genereer 2 willekeurige Pokémons met je generator en laat ze vechten met je battle-methode. Toon wat output aan de gebruiker zodat hij ziet wat er allemaal gebeurt (en gebruik zeker de ShowInfo methode om dit snel te doen). Kan je dit in een loop zetten en wat leuker maken met Pokémons die telkens levelen als ze een gevecht winnen?!

Stack en Heap

hashtag
Geheugenmanagement in C-Sharp

Tot nog toe lagen we er niet van wakker wat er achter de schermen van een C# programma gebeurde. We duiken nu dieper in wat er juist gebeurt wanneer we variabelen aanmaken.

hashtag

Student stud1;
stud1.Naam= "Test";
SpecialAttack_Base
  • SpecialDefense_Base

  • Speed_Base

  • Vraag nu aan de gebruiker tot welke level de Pokémon moet gelevelled worden. Roep zoveel keer de LevelUp-methode aan van de Pokémon. (of kan je dit via een parameter doorgeven aan LevelUp?!)

  • Toon terug de full-stats van de nu ge-levelde Pokémon

  • deze lijstarrow-up-right
    met volgende oplossingarrow-up-right
    volgende uitlegarrow-up-right
    Pokémon
    Twee soorten geheugen

    Wanneer een C# applicatie wordt uitgevoerd krijgt het twee soorten geheugen toegewezen dat het 'naar hartelust' kan gebruiken:

    1. Het kleine, maar snelle stack geheugen

    2. Het grote, maar tragere heap geheugen

    Afhankelijk van het soort variabele wordt ofwel de stack, ofwel de heap gebruikt. Het is uitermate belangrijk dat je weet in welk geheugen de variabele zal bewaard worden!

    Er zijn namelijk twee soorten variabelen:

    1. Value types

    2. Reference types

    Als je volgende tabel begrijpt dan beheers je geheugenmanagement in C#:

    Value types

    Reference types

    Inhoud van de variabele

    De eigenlijke data

    Een referentie naar de eigenlijke data

    Locatie

    (Data) Stack

    Heap (globaal)geheugen

    Beginwaarde

    0,0.0, "",false, etc.

    null

    Effect van = operator

    hashtag
    Waarom twee geheugens?

    Waarom plaatsen we niet alles in de stack? De reden hiervoor is dat bij het compileren van je applicatie er reeds zal berekend worden hoeveel geheugen de stack zal nodig hebben. Wanneer je programma dus later wordt uitgevoerd weet het OS perfect hoeveel geheugen het minstens moet reserveren. Er is echter een probleem: we kunnen niet alles perfect berekenen/voorspellen. Een variabele van het type int is perfect geweten hoe groot die zal zijn (32 bit).Maar wat met een string? Of met een array waarvan we pas tijdens de uitvoer de lengte aan de gebruiker misschien vragen? Het zou nutteloos (en zonde) zijn om reeds bij aanvang een bepaalde hoeveelheid voor een array te reserveren als we niet weten hoe groot die zal worden. Beeld je maar eens in dat we 2k byte reserveren om dan te ontdekken dat we maar 5byte ervan nodig hebben. RAM is goedkoop, maar toch... De heap laat ons dus toe om geheugen op een wat minder gestructureerde manier in te palmen. Tijdens de uitvoer van het programma zal de heap als het ware dienst doen als een grote zandbak waar eender welke plek kan ingepalmd worden om zaken te bewaren. De stack daarentegen is het kleine bankje naast de zandbak: handig, snel, en perfect geweten hoe groot.

    hashtag
    Value types

    Value types worden in de stack bewaard. De effectieve waarde van de variabele wordt in de stack bewaard. Dit zijn alle gekende, 'eenvoudige' datatypes die we totnogtoe gezien hebben, inclusief enums en structs (zie later):

    • sbyte, byte

    • short, ushort

    • int, uint

    • long, ulong

    • char

    • float, double, decimal

    • bool

    • structs (zien we niet in deze cursus)

    • enums

    = operator bij value types

    Wanneer we een value-type willen kopieren dan kopieren de echte waarde:

    Vanaf nu zal anderGetal de waarde 3 hebben. Als we nu een van beide variabelen aanpassen dan zal dit geen effect hebben op de andere variabelen.

    We zien het zelfde effect wanneer we een methode maken die een parameter van het value type aanvaardt- we geven een kopie van de variabele mee:

    De parameter a zal de waarde 5 gekopieerd krijgen. Maar wanneer we nu zaken aanpassen in a zal dit geen effect hebben op de waarde van getal. De output van bovenstaand programma zal zijn:

    hashtag
    Reference types

    Reference types worden in de heap bewaard. De effectieve waarde wordt in de heap bewaard, en in de stack zal enkel een referentie of pointer naar de data in de heap bewaard worden. Een referentie (of pointer) is niet meer dan het geheugenadres naar waar verwezen wordt (bv 0xA3B3163) Concreet zijn dit alle zaken die vaak redelijk groot zullen zijn:

    • objecten, interfaces en delegates

    • arrays

    = operator bij reference types

    Wanneer we de = operator gebruiken bij een reference type dan kopieren we de referentie naar de waarde, niet de waarde zelf.

    Bij objecten We zien dit gedrag bij alle reference types, zoals objecten:

    Wat gebeurt er hier?

    1. new Student() : new roept de constructor van Student aan. Deze zal een constructor in de heap aanmaken en vervolgens de geheugenlocatie teruggeven.

    2. Een variabele stud wordt in de stack aangemaakt.

    3. De geheugenlocatie uit de eerste stap wordt vervolgens in stud opgeslagen in de stack.

    Bij arrays

    Maar ook bij arrays:

    In dit voorbeeld zal andereNummers dus nu ook verwijzen naar de array in de heap waar de actuele waarden staan.

    Als we dus volgende code uitvoeren dan ontdekken we dat beide variabele naar dezelfde array verwijzen:

    We zullen dus als output krijgen:

    Hetzelfde gedrag zien we bij objecten:

    We zullen in dit geval dus Queen op het scherm zien omdat zowel b als a naar het zelfde object in de heap verwijzen. Het originele "abba"-object zijn we kwijt en zal verdwijnen (zie Garbage collector verderop).

    Methoden en reference parameters

    Ook bij methoden geven we de dus de referentie naar de waarde mee. In de methode kunnen we dus zaken aanpassen van de parameter en dan passen we eigenlijk de originele variabele aan:

    We krijgen als uitvoer:

    Opgelet: Wanneer we een methode hebben die een value type aanvaardt en we geven één element van de array met dan geven dus een kopie van de actuele waarde mee!

    De output bewijst dit:

    hashtag
    De Garbage Collector (GC)

    Een essentieel onderdeel van .NET is de zogenaamde GC, de Garbage Collector. Dit is een geautomatiseerd onderdeel van ieder C# programma dat ervoor zorgt dat we geen geheugen voor niets gereserveerd houden. De GC zal geregeld het geheugen doorlopen en kijken of er in de heap data staat waar geen references naar verwijzen. Indien er geen references naar wijzen zal dit stuk data verwijderd worden.

    In dit voorbeeld zien we dit in actie:

    Vanaf de laatste lijn zal er geen referentie meer naar {3,4,5} in de heap zijn, daar we deze hebben overschreven met een referentie naar {1,2,3}.De GC zal dus deze data verwijderen.

    Wil je dat niet dan zal je dus minstens 1 variabele moeten hebben dat naar de data verwijst. Volgende voorbeeld toont dit:

    De variabele bewaarArray houdt dus een referentie naar {3,4,5} bij en we kunnen dus later via deze variabele alsnog aan de originele data.

    hashtag
    Meer weten?

    Meer info, lees zeker volgende artikels:

    • Reference en value typesarrow-up-right

    • Stack vs heaparrow-up-right

    NullReferenceException error in VS
    Pikachu (level 5)
    Base stats:
        * Health = 56
        * Speed = 30
        etc
    Full stats:
        * Health = 100
        etc.
    int getal=3;
    int anderGetal= getal;
    void DoeIets(int a)
    {
        a++;
        Console.WriteLine($"In methode {a}");
    }
    
    //Elders:
    int getal= 5;
    DoeIets(getal);
    Console.WriteLine($"Na methode {getal}");
    In methode 6
    Na methode 5
    Student stud= new Student();
    int[] nummers= {4,5,10};
    int[] andereNummers= nummers;
    andereNummers[0]=999;
    Console.WriteLine(andereNummers[0]);
    Console.WriteLine(nummers[0]);
    999
    999
    Student a= new Student("Abba");
    Student b= new Student("Queen");
    a=b;
    Console.WriteLine(a.Naam);
    void DoeIets(int[] a)
    {
       a[0]++;
       Console.WriteLine($"In methode {a[0]}");
    }
    
    //Elders:
    int[] getallen= {5,3,2};
    DoeIets(getallen);
    Console.WriteLine($"Na methode {getallen[0]}");
    In methode 6
    Na methode 6
    void DoeIets(int a)
    {
        a++;
        Console.WriteLine($"In methode {a}");
    }
    
    //Elders:
    int[] getallen= {5,3,2};
    DoeIets(getallen[0]); //<= VALUE TYPE!
    Console.WriteLine($"Na methode {getallen[0]}");
    In methode 6
    Na methode 5
    int[] array1= {1,2,3};
    int[] array2= {3,4,5};
    array2=array1;
    int[] array1= {1,2,3};
    int[] array2= {3,4,5};
    int[] bewaarArray= array2;
    array2=array;
    Student stud1 = null;
    
    Console.WriteLine(stud1.Name);
    if(stud1 == null)
        Console.WriteLine("oei. object bestaat niet")
    if(stud1 != null)
    {
        Console.WriteLine(stud1.Name)
    }
    Console.WriteLine(stud1?.Name)
    static Student ZoekStudent(Student[] array, string naam)
    {
        for (int i = 0; i < array.Length; i++)
        {
            if (array[i].Name == naam)
                return array[i];
        }
    
        return null;
    }

    Kopieert de actuele waarde

    Kopieert het adres naar de actuele waarde