TOC

This article is currently in the process of being translated into Polish (~8% done).

Advanced topics:

Exception handling

W każdym programie czasami coś nie działa tak jak powinno. W języku C# naszym zbawieniem jest dobry kompilator, który pomaga nam zapobiegać najpopularniejszym błędom. Oczywiście nie widzi każdego błędu który może się pojawić, z tego powodu .NET framework rzuca wyjątki, aby powiedzieć nam, że coś poszło nie tak. W poprzednim rozdziale, o tablicach, opisałem jak otrzymalibyśmy wyjątek gdybyśmy próbowali upchnąć zbyt wiele elementów do tablicy. Podajmy przykład:

using System;
using System.Collections;

namespace ConsoleApplication1
{
    class Program
    {
static void Main(string[] args)
{
    int[] numbers = new int[2];

    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
    Console.ReadLine();
}
    }
}

Okay, try running this example, and you will see what I'm talking about. Do you see what we're doing wrong? We have defined an array of integers with room for 2 items, yet we try to use 3 spaces in it. Obviously, this leads to an error, which you will see if you try to run this example. When run inside Visual Studio, the IDE gives us some options for the exception, but if you try to execute the program by simply double-clicking the EXE file, you will get a nasty error. If you know that an error might occur, you should handle it. This is where exceptions are used. Here is a slightly modified version of the code from above:

int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
}
catch
{
    Console.WriteLine("Something went wrong!");
}
Console.ReadLine();

Let me introduce to you your new best friend when it comes to error handling: the try..catch block. Try running the program now, and see the difference - instead of Visual Studio/Windows telling us that a serious problem occurred, we get to tell our own story. But wouldn't it be nice if we could tell what went wrong? No problem:

catch(Exception ex)
{
    Console.WriteLine("An error occured: " + ex.Message);
}

As you can see, we have added something to the catch statement. We now tell which exception we want caught, in this case the base of all exceptions, the Exception. By doing so, we get some information about the problem which caused the exception, and by outputting the Message property, we get an understandable description of the problem.

As I said, Exception is the most general type of exception. The rules of exception handling tells us that we should always use the least general type of exception, and in this case, we actually know the exact type of exception generated by our code. How? Because Visual Studio told us when we didn't handle it. If you're in doubt, the documentation usually describes which exception(s) a method may throw. Another way of finding out is using the Exception class to tell us - change the output line to this:

Console.WriteLine("An error occured: " + ex.GetType().ToString());

The result is, as expected, IndexOutOfRangeException. We should therefore handle this exception, but nothing prevents us from handling more than one exception. In some situations you might wish to do different things, depending on which exception was thrown. Simply change our catch block to the following:

catch(IndexOutOfRangeException ex)
{
    Console.WriteLine("An index was out of range!");
}
catch(Exception ex)
{
    Console.WriteLine("Some sort of error occured: " + ex.Message);
}

As you can see, we look for the IndexOutOfRangeException first. If we did it the other way around, the catch block with the Exception class would get it, because all exceptions derive from it. So in other words, you should use the most specific exceptions first.

One more thing you should know about concerning exceptions is the finally block. The finally block can be added to a set of catch blocks, or be used exclusively, depending on your needs. The code within the finally block is always run - exception or no exception. It's a good place if you need to close file references or dispose objects you won't need anymore. Since our examples have been pretty simple so far, we haven't really been in need of any cleanup, since the garbage collector handles that. But since will likely run into situations where you need the finally block, here is an extended version of our example:

int[] numbers = new int[2];
try
{
    numbers[0] = 23;
    numbers[1] = 32;
    numbers[2] = 42;

    foreach(int i in numbers)
Console.WriteLine(i);
}
catch(IndexOutOfRangeException ex)
{
    Console.WriteLine("An index was out of range!");
}
catch(Exception ex)
{
    Console.WriteLine("Some sort of error occured: " + ex.Message);
}
finally
{
    Console.WriteLine("It's the end of our try block. Time to clean up!");
}
Console.ReadLine();

If you run the code, you will see that both the first exception block and the finally block is executed. If you remove the line that adds the number 42 to the array, you will see that only the finally block is reached.

Another important part you should know about exceptions, is how they impact the method in which the exceptions occur. Not all unhandled exceptions are fatal for your application, but when they aren't, you should not expect the remaining code of the method to be executed. On the other hand, if you do handle the exception, only the lines after the try block will be executed. In our example, the loop that outputs the values of the array is never reached, because the try block goes straight to the catch/finally block(s) once an exception is thrown. However, the last line, where we read from the console to prevent the application from exiting immediately, is reached. You should always have this in mind when you construct try blocks.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!