Using Exceptions (C# Programming Guide)

In C#, errors in the program at run time are propagated through the program by using a mechanism called exceptions. Exceptions are thrown by code that encounters an error and caught by code that can correct the error. Exceptions can be thrown by the .NET Framework common language runtime (CLR) or by code in a program. Once an exception is thrown, it propagates up the call stack until a catch statement for the exception is found. Uncaught exceptions are handled by a generic exception handler provided by the system that displays a dialog box.

Exceptions are represented by classes derived from Exception. This class identifies the type of exception and contains properties that have details about the exception. Throwing an exception involves creating an instance of an exception-derived class, optionally configuring properties of the exception, and then throwing the object by using the throw keyword. For example:

class CustomException : Exception
{
    public CustomException(string message)
    {

    }

}
private static void TestThrow()
{
    CustomException ex =
        new CustomException("Custom exception in TestThrow()");

    throw ex;
}

After an exception is thrown, the runtime checks the current statement to see whether it is within a try block. If it is, any catch blocks associated with the try block are checked to see whether they can catch the exception. Catch blocks typically specify exception types; if the type of the catch block is the same type as the exception, or a base class of the exception, the catch block can handle the method. For example:

static void TestCatch()
{
    try
    {
        TestThrow();
    }
    catch (CustomException ex)
    {
        System.Console.WriteLine(ex.ToString());
    }
}

If the statement that throws an exception is not within a try block or if the try block that encloses it has no matching catch block, the runtime checks the calling method for a try statement and catch blocks. The runtime continues up the calling stack, searching for a compatible catch block. After the catch block is found and executed, control is passed to the next statement after that catch block.

A try statement can contain more than one catch block. The first catch statement that can handle the exception is executed; any following catch statements, even if they are compatible, are ignored. Therefore, catch blocks should always be ordered from most specific (or most-derived) to least specific. For example:

static void TestCatch2()
{
    System.IO.StreamWriter sw = null;
    try
    {
        sw = new System.IO.StreamWriter(@"C:\test\test.txt");
        sw.WriteLine("Hello");
    }

    catch (System.IO.FileNotFoundException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the more specific exception first
    }

    catch (System.IO.IOException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the less specific exceptions last
    }
    finally 
    {
        sw.Close();
    }

    System.Console.WriteLine("Done");  // this statement is executed after the catch block
}

Before the catch block is executed, the runtime checks for finally blocks. Finally blocks enable the programmer to clean up any ambiguous state that could be left over from an aborted try block, or to release any external resources (such as graphics handles, database connections or file streams) without waiting for the garbage collector in the runtime to finalize the objects. For example:

static void TestFinally()
{
    System.IO.FileStream file = null;
    //Change the path to something that works on your machine
    System.IO.FileInfo fileInfo = new System.IO.FileInfo(@"C:\file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown. 
        if (file != null)
        {
            file.Close();
        }
    }

    try
    {
        file = fileInfo.OpenWrite();
        System.Console.WriteLine("OpenWrite() succeeded");
    }
    catch (System.IO.IOException)
    {
        System.Console.WriteLine("OpenWrite() failed");
    }
}

If WriteByte() threw an exception, the code in the second try block that tries to reopen the file would fail if file.Close() is not called, and the file would remain locked. Because finally blocks are executed even if an exception is thrown, the finally block in the previous example allows for the file to be closed correctly and helps avoid an error.

If no compatible catch block is found on the call stack after an exception is thrown, one of three things occurs:

  • If the exception is within a destructor, the destructor is aborted and the base destructor, if any, is called.

  • If the call stack contains a static constructor, or a static field initializer, a TypeInitializationException is thrown, with the original exception assigned to the InnerException property of the new exception.

  • If the start of the thread is reached, the thread is terminated.

See Also

Concepts

C# Programming Guide

Reference

Exceptions and Exception Handling (C# Programming Guide)