arrow-left

All pages
gitbookPowered by GitBook
1 of 5

Loading...

Loading...

Loading...

Loading...

Loading...

Null en NullReferenceException

hashtag
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
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):

hashtag
Kennisclip

Student stud1;
stud1.Naam= "Test";
Referenties en nullarrow-up-right
NullReferenceException error in VS

Oefeningen

hashtag
Oefening 1: klasse Pokemon (h9-pokeattack)

hashtag
Leerdoelen

  • herhalen aanmaken klassen

  • herhalen aanmaken instantiemethoden

  • herhalen aanmaken full properties

hashtag
Functionele analyse

We willen een bootleg Pokémon spel maken. We starten met een klasse om Pokémon voor te stellen.

hashtag
Technische analyse

Schrijf een klasse Pokemon met volgende onderdelen:

  • een full property MaxHP

    • deze stelt een getal voor dat altijd minstens 20 en maximum 1000 bedraagt

    • als je een lagere of hogere waarde probeert in te stellen, wordt de dichtstbijzijnde waarde ingesteld

Schrijf dan een statische MakePokemon-methode in de klasse Pokemon die één Pokemon van elke soort maakt (je mag de soort en het type invullen na het aanmaken van de objecten) en elk van deze Pokemon één keer hun methode Attack laat uitvoeren. Elke Pokemon start bovendien met 20 hit points als huidige waarde en als maximumwaarde.

hashtag
Voorbeeldinteractie

(In groen, rood, blauw en geel.)

hashtag
Oefening 2 (h9-consciouspokemon)

hashtag
Leerdoelen

  • arrays van objecten

  • null

hashtag
Functionele analyse

In een gevecht begin je met je eerste Pokémon die nog bij bewustzijn is. Bewusteloze Pokémon kunnen immers niet vechten. Schrijf een statische methode om de eerste bewuste Pokémon te vinden.

hashtag
Technische analyse

Schrijf een statische methode FirstConsciousPokemon met één parameter: een array van Pokemon. Deze methode loopt met een for-lus door de array en geeft als antwoord de eerste Pokemon terug met minstens 1 HP. Je moet zorgen dat de methode aanvaard wordt door de compiler, ook als er geen enkele bewuste Pokémon in de rij is bijgehouden.

Schrijf ook een statische methode TestConsciousPokemon() die een array van dezelfde vier Pokémon als hierboven maakt, waarbij Bulbasaur en Charmander 0 HP hebben en Squirtle 2 HP. Toon wat gebeurt als de eerste wakkere Pokémon aanvaalt. Dit is de methode die je vanuit je keuzemenu zal oproepen.

hashtag
Voorbeeldinteractie

hashtag
Oefening 3 (h9-consciouspokemon-improved)

hashtag
Leerdoelen

  • arrays van objecten

  • null

hashtag
Functionele analyse

Je moet ook het geval afhandelen waarbij al je Pokémon KO zijn.

hashtag
Technische analyse

Breid je methode TestConsciousPokemon uit zodat ze niet crasht wanneer al je Pokémon KO zijn. Doe dit in een nieuwe versie, TestConsciousPokemonSafe.

hashtag
Voorbeeldinteractie

hashtag
Oefening 4: value en reference (h9-pokevalueref)

hashtag
Leerdoelen

  • call by value vs. call by reference

hashtag
Functionele analyse

Een beginnend programmeur bij Game Freak heeft volgende statische methode geschreven in je klasse:

Hij gaat ervan uit dat dit werkt:

Maar dit klopt niet. Los zijn bug op.

hashtag
Technische analyse

Je moet RestoreHP anders schrijven en ook het gebruik ervan aanpassen. Je mag de parameters van RestoreHP volledig aanpassen en ook de eerste for-lus veranderen. De tweede for-lus en het aanmaken van de array van Pokemon moeten exact gebeuren zoals ze geschreven zijn.

Roep DemoRestoreHP() op uit je keuzemenu.

hashtag
Voorbeeldinteractie

hashtag
Oefening 5: uitkomst gevecht (h9-fight)

hashtag
Leerdoelen

  • gebruik van Random

  • null guard

  • call by reference

hashtag
Functionele analyse

Hoe wreed het ook is, Pokémon zijn bestemd om tegen elkaar te vechten. Schrijf een simulatie van een gevecht met een willekeurig element.

hashtag
Technische analyse

Schrijf eerst een enumeratie FightOutcome met drie mogelijkheden: WIN, LOSS en UNDECIDED ("onbeslist").

Schrijf dan een statische methode FightOutcome in de klasse Pokemon. Deze heeft drie parameters, twee Pokemon-objecten en één Random-object.

FightOutcome() werkt als volgt:

  • Een van de twee Pokémon mag eerst aan de beurt; welke van de twee wordt willekeurig beslist met behulp van het Random-object.

  • Wanneer een Pokémon aan de beurt is, voert hij zijn Attack() methode uit.

  • Hierna verlaagt de HP van de andere Pokémon met een getal tussen 0 en 20.

Handel ook situaties af waarbij minstens één van de twee Pokémon null is of al KO is bij het begin van de match. Dan wint de andere vanzelf, tenzij ze allebei ontbreken of KO zijn. Dan is de uitkomst UNDECIDED.

Schrijf ten slotte een methode DemoFightOutcome() die twee Pokémon naar keuze aanmaakt, hen tegen elkaar laat vechten tot er een resultaat is en dat resultaat dan op het scherm toont.

Test je methode met alle combinaties:

  • twee gezonde Pokémon

  • één bewusteloos

  • twee bewusteloos

hashtag
Afronden

Ga na dat al je code van de eerste oefeningen nog werkt nadat je de laatste hebt afgerond en plaats alles op Bitbucket.

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:

Dan 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
Onderdelen 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 dan als volgt 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:

hashtag
Kennisclip

  • (opname uit hoorcollege 4/3/20)

Stack en Heap

hashtag
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
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#:

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.

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

= 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:

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.

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:

hashtag
Kennisclip

Student stud1 = null;

Console.WriteLine(stud1.Name);
if(stud1 == null)
    Console.WriteLine("oei. object bestaat niet")
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;
}
class Meting
{
    public int Temperatuur { get; set; }
    public string OpgemetenDoor { get; set; }
}

een full property HP

  • deze stelt een getal voor dat altijd groter dan of gelijk aan 0 is; verder kan de waarde ook nooit groter gemaakt worden dan MaxHP; elke poging om het getal kleiner dan 0 te maken, maakt het gelijk aan 0 en elke poging om boven MaxHP te gaan, maakt het gelijk aan MaxHP.

  • een autoproperty PokeSpecies om aan te geven over welk soort Pokémon het gaat; maak hiervoor een enum PokeSpecies met waarden Bulbasaur, Charmander, Squirtle, Pikachu

  • een autoproperty PokeType om aan te geven wat het element van de Pokémon is; maak hiervoor een enum PokeTypes met waarden Grass, Fire, Water, Electric

  • een methode Attack(): deze zorgt ervoor dat de naam van het soort Pokémon in hoofdletters en in kleur wordt geprint. Je kan de methode ToString() van een enum gebruiken. De kleur die je gebruikt is als volgt:

    • groen voor type Grass

    • rood voor type Fire

    • blauw voor Water

    • geel voor Electric

  • Hierna is de andere van de twee Pokémon aan de beurt, maar alleen als hij nog bij bewustzijn is.

  • De match is voorbij wanneer één van de twee Pokémon 0 HP heeft bereikt. Dan wordt het resultaat teruggegeven:

    • WIN als de eerste Pokémon die je als parameter hebt meegegeven nog bij bewustzijn is.

    • LOSS als de tweede nog bij bewustzijn is.

  • één null
  • twee null

  • één bewusteloos en één null

  • BULBASAUR!
    CHARMANDER!
    SQUIRTLE!
    PIKACHU!
    SQUIRTLE!
    Al je Pokémon zijn KO! Haast je naar het Pokémon center.
    public static void RestoreHP(int oldHP, int newHP) {
        oldHP = newHP;
    }
    public static void DemoRestoreHP() {
    // aanmaken van array bewusteloze Pokemon van 4 soorten zoals eerder: zelf doen
    for(int i = 0; i < pokemon.Length; i++) {
        Pokemon.RestoreHP(pokemon[i].HP,pokemon[i].MaxHP);
        }
    }
    for(int i = 0; i < pokemon.Length; i++) {
        Console.WriteLine(pokemon[i].HP)
        }
    }
    20
    20
    20
    20

    Kopieert het adres naar de actuele waarde

    int, uint
  • long, ulong

  • char

  • float, double, decimal

  • bool

  • structs (zien we niet in deze cursus)

  • enums

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

    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

    Reference en value typesarrow-up-right
    Stack vs heaparrow-up-right
    Stack vs heaparrow-up-right

    Kopieert de actuele waarde

    Objecten als parameter of returnwaardearrow-up-right
    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;
    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);
    }

    H9: Geheugenmanagement bij klassen