Exception handling

Het wordt tijd om de olifant in de kamer te benoemen. Het wordt tijd om een bekentenis te maken... Ben je er klaar voor?! Hier komt ie. Luister goed, maar zeg het niet door: ik heb al de hele tijd informatie voor je achter gehouden! Sorry, het was sterker dan mezelf. Maar ik deed het voor jou. Het was de enige manier om ervoor te zorgen dat je leerde programmeren zonder constant bugs in je code achter te laten. Dus ja, hopelijk neem je het me niet kwalijk?

Het wordt tijd om exception handling er bij te halen! Een essentiële programmeertechniek die ervoor zorgt dat je programma minder snel zal crashen indien er zich uitzonderingen tijdens de uitvoer voordoen.

Wat een dramatische start zeg. Waar was dat voor nodig?! De reden is eenvoudig: exception handling is een tweesnijdend zwaard. Je zou exception handling kunnen gebruiken om al je bugs op te vangen, zodat de eindgebruiker niet ziet hoe vaak je programma zou crashen zonder exception handling.

Maar uiteindelijk blijf je wel met slechte code zitten en een gouden regel in programmeren is dat slechte code je altijd zal achtervolgen en je ooit dubbel en hard zal straffen voor iedere bug waar je te lui voor was om op te lossen. Kortom, exception handling is de finale fase van goedgeschreven code.

Waarom exception handling?

Veel fouten in je code zijn het gevolg van:

  • Het aanroepen van data die er niet is : bijvoorbeeld een bestand dat werd verplaatst of hernoemd . Of wat denk van het wegvallen van het wifi-signaal net wanneer je programma iets van een online database nodig heeft.
  • Foute invoer door de gebruiker : denk aan de gebruiker die een letter invoert terwijl het programma een getal verwacht.
  • Programmeerfouten : de ontwikkelaar gebruikt een object dat nog niet met de new operator werd geïnitialiseerd. Of bijvoorbeeld een deling door nul in een wiskundige berekening.

Voorgaande zaken zijn eigenlijk geen fouten, maar uitzonderingen (exceptions). Ze doen zich zelden voor, maar hebben wel een invloed op de correcte uitvoer van je programma. Je programma zal met deze uitzonderingen rekening moeten houden wil je een gebruiksvriendelijk programma hebben. Veel uitzonderingen gebeuren buiten de wil van het programma om (geen wifi , foute invoer, enz.). Door deze uitzonderingen af te handelen (exception handling), kunnen we ons programma instructies geven om alternatieve acties uit te voeren wanneer er een uitzondering optreedt.

Je hebt waarschijnlijk al eerder exceptions gezien in je eigen programma's. Als je programma plots een hele hoop tekst toont (waaronder het woord "Exception") en vervolgens direct afsluit, heb je een exception gegenereerd die niet is afgehandeld.

Een joekel van een foutboodschap die je gebruiker huilend zal wegjagen.

Je moet zelfs niet veel moeite doen om uitzonderingen te genereren. Denk maar aan volgende voorbeeld waarbij je een exception kan genereren door een 0 in te geven, of iets anders dan een getal.

Console.WriteLine("Geef een getal aub");
int noemer = Convert.ToInt32(Console.ReadLine());
double resultaat = 100/noemer;
Console.WriteLine($"100/{noemer} is gelijk aan {resultaat}");

Exception handling met try en catch

Het mechanisme om exceptions af te handelen in C# bestaat uit 2 delen:

  • Een try blok: binnen dit blok staat de code die je wil controleren op uitzonderingen omdat je weet dat die hier kunnen optreden.
  • Een of meerdere catch-blokken: dit blok zal mogelijk exceptions die in het bijhorende try-block voorkomen opvangen. Met andere woorden: in dit blok staat de code die de uitzondering zal verwerken zodat het programma op een deftige manier verder kan of meer elegant zichzelf afsluiten (graceful shutdown).

De syntax is als volgt (let er op dat de catch blok onmiddellijk na het try-blok komt):

try
{
    //code waar exception mogelijk kan optreden
}
catch 
{
    //exception handling code hier
}

Een try catch voorbeeld

In volgend stukje code kunnen uitzonderingen optreden zoals we zonet zagen:

string input = Console.ReadLine();
int converted = Convert.ToInt32(input)

Een FormatException zal optreden wanneer de gebruiker tekst of een kommagetal invoert. De Convert.ToInt32() methode kan niet met andere input dan gehele getallen werken.

Ik toon nu hoe we dit met exception handling kunnen opvangen1:

try
{
    string input = Console.ReadLine();
    int converted = Convert.ToInt32(input);
}
catch 
{
    Console.WriteLine("Verkeerde invoer!");
}

Indien er nu een uitzondering optreedt dan zal de tekst "Verkeerde invoer" getoond worden. Vervolgens gaat het programma verder met de code die mogelijk na het catch-blok staat.

1

Merk op dat dit probleem eleganter kan opgelost worden met TryParse wat in het appendix wordt uitgelegd.

Een exception genereren met throw

Je kan ook zelf eender waar in je code een uitzondering opwerpen. Je doet dit met het throw keyword. De werking is quasi dezelfde als het return keyword. Alleen zal bij een throw je terug gaan tot de eerste plek waar een catch klaarstaat om de uitzondering op te vangen. Om een uitzondering op te werpen dien je eerst een Exception object aan te maken en daar de nodige informatie in te plaatsen. In hoofdstuk 14 ga ik hier nog wat dieper op in, maar hier alvast een voorbeeldje:

//Een error treedt op
throw new Exception("Wow, dit loopt fout");

Afhankelijk van het soort fout kunnen we echter ook andere soort uitzonderingen opwerpen. Draai daarom snel deze pagina om en ontdek hoe dit kan!

Meerdere catchblokken

Exception is een klasse van het .NET framework. Er zijn van deze basis-klasse meerdere Exception-klassen afgeleid die een specifieke uitzondering behelzen. Enkele veelvoorkomende zijn:

KlasseOmschrijving
ExceptionBasisklasse
SystemExceptionKlasse voor uitzonderingen die niet al te belangrijk zijn en die mogelijk verholpen kunnen worden.
IndexOutOfRangeExceptionDe index is te groot of te klein voor de benadering van een array
NullReferenceExceptionBenadering van een niet-geïnitialiseerd object

Je kan in het catch blok aangeven welke soort exceptions je wil vangen in dat blok. Als je bijvoorbeeld alle Exceptions wil opvangen schrijf je:

catch (Exception e)
{
}

Hiermee vangen we dus alle Exceptions op, daar alle Exceptions van de klasse Exception afgeleid zijn en dus ook zelf een Exception zijn. De identifier e kies je zelf en wordt gebruikt om vervolgens in het catch block de nodige informatie uit het opgevangen Exception object (e) uit te lezen. Ik leg dit zo meteen uit.

We kunnen nu echter ook specifieke exceptions opvangen. De truc is om de meest algemene exception onderaan te zetten en naar boven toe steeds specifieker te worden. We maken een fallthrough mechanisme (wat we ook in een switch al hebben gezien).

Stel bijvoorbeeld dat we weten dat de FormatException kan voorkomen en we willen daar iets mee doen. Volgende code toont hoe dit kan:

try
{
    //...
}
catch (FormatException e)
{
    Console.WriteLine("Verkeerd invoerformaat");
}
catch (Exception e)
{
    Console.WriteLine("Exception opgetreden");
}

Indien een FormatException optreedt dan zal het eerste catch-blok uitgevoerd worden, in alle andere gevallen het tweede. Het tweede blok zal niet uitgevoerd worden indien een FormatException optreedt.

Welke exceptions worden gegooid?

De online .NET documentatie is de manier om te weten te komen welke exceptions een methode mogelijk kan opgooien. Gaan we bijvoorbeeld naar de documentatie van de Int32.Parse-methode2 dan zien we daar een sectie "Exceptions" waar klaar en duidelijk wordt beschreven wanneer welke exception wanneer wordt opgeworpen.

Een deel van de documentatie.

Werken met de exception parameter

De Exceptions die worden opgegooid door een methode zijn objecten van de Exception-klasse. Deze klasse bevat standaard een aantal interessante zaken, die je kan oproepen in je code.

Bovenaan de declaratie van het catch-blok geef je aan hoe het exception object in het blok zal heten. In de vorige voorbeelden was dit altijd e (standaardnaam).

Alle Exception-objecten bevatten volgende informatie:

ElementOmschrijving
MessageFoutmelding in relatief eenvoudige taal.
StackTraceLijst van methoden die de exception hebben doorgegeven.
TargetSiteMethode die de exception heeft gegenereerd (staat bij StackTrace helemaal bovenaan).
ToString()Geeft het type van de exception, Message en StackTrace terug als string.

We kunnen via deze parameter meer informatie uit de opgeworpen uitzondering uitlezen en bijvoorbeeld aan de gebruiker tonen:

catch (Exception e)
{
    Console.WriteLine("Exception opgetreden");
    Console.WriteLine("Message:"+e.Message);
 
    Console.WriteLine($"Targetsite: {e.TargetSite}");
    Console.WriteLine("StackTrace: {e.StackTrace}");
}

Vanuit een security standpunt is het zelden aangeraden om Exception informatie zomaar rechtstreeks naar de gebruiker te sturen. Mogelijk bevat de informatie gevoelige informatie en zou deze door kwaadwillige gebruikers kunnen misbruikt worden om bugs in je programma te vinden.