Since we now know a bit about C# classes, we can learn how to write code that handles unexpected situations and errors. This process is called exception handling, and is a part of all but the most basic C# programs.

Let's learn about exceptions and how C# allows us to handle them!

A closeup of two hands against a black background.
Better watch out, it's catching. Photo by Luis Quintero / Unsplash

The Sample Solution

exceptionnotfound/CSharpInSimpleTerms
Contribute to exceptionnotfound/CSharpInSimpleTerms development by creating an account on GitHub.
Project for this post: 12ExceptionHandling

Exceptions

An exception is an unexpected error that happens while a C# program is running. The C# runtime will transform an exception into an instance of a class, and we can use that instance to try to debug why the error happened.

All instances of an exception in C# must inherit from the class System.Exception. When an exception is encountered while a program is running, we say it has been thrown.

C# includes a huge variety of built-in exception classes. For example, if we attempt to divide by zero, an instance of System.DivideByZeroException will be thrown.

public class Program
{
    static void Main(string[] args)
    {
        int zero = 0;
        int two = 2;

        int result = two / zero; //Throws System.DivideByZeroException
    }
}

Understanding Exceptions

When an exception is thrown, the C# compiler will include information in that exception instance which is helpful when attempting to find out where the exception was thrown from. This includes:

  • Stack Trace - The classes and methods that were called in order to execute the line of code that threw the exception. We use this to trace back and find out what kinds of arguments or method calls might have led to this exception being thrown.
  • Error Message - A short message explaining what the exception was.
  • Inner Exceptions - Exceptions can be wrapped in other exceptions. When this happens, the innermost exception (i.e. the exception that does not have any further inner exceptions) is often the most useful for debugging.

Handling Thrown Exceptions

If we have a block or line of code that we know might throw an exception at runtime, we can wrap it using the try keyword. Any exception which is caught in the try block can then be processed in a corresponding catch block.

try
{
    var two = 2;
    var zero = 0;
    
    var result = two / zero; //Will throw DivideByZeroException
    
    CallOtherMethod(); //Never invoked
}
catch(Exception ex)
{
    //Code that will "handle" the exception.
}

Execution of the code in the try block stops when an exception is encountered. In the above code, the method CallOtherMethod() will never be invoked, because the line before it will always throw an exception.

The catch keyword allows us to handle thrown exceptions. "Handling" an exception can mean many things. For example, we might want to log that the exception occurred in an error-logging system, redirect the code execution to a different method or block, or some combination of the two.

try
{
    var two = 2;
    var zero = 0;
    
    var result = two / zero; //Will throw DivideByZeroException
    
    CallOtherMethod(); //Never invoked
}
catch(Exception ex)
{
    var errorLogger = new MyErrorLogger();
    errorLogger.Log(ex);
}

Swallowing Exceptions

Choosing to do nothing with a caught exception is called "swallowing" the exception and is generally considered bad practice:

try
{
    var result = two / zero; //Will throw DivideByZeroException
}
catch (Exception ex)
{
    //Swallowing exceptions is a bad practice!
}

However, something being bad practice does not mean that we should never do it. It means we should try not to do it, or only do it in specific situations where we know and understand the risks or complications that might result.

In the case of swallowing exceptions, the primary risk is that errors might occur, and because they were swallowed, we will have no idea that they even happened.

Multiple Catch Blocks

We can also catch specific kinds of exceptions and execute different code blocks for each. The most-specific exception needs to be listed first, and the least-specific goes last.

try
{
    //Code that might throw an exception
}
catch (DivideByZeroException ex)
{
    //Handle the divide by zero situation
}
catch (ArgumentException ex)
{
    //Handle the argument exception situation
}
catch (Exception ex)
{
    //Handle all other exceptions
}

Finally Blocks

Before the execution of a try catch block ends, the C# compiler will check for the existence of a finally block. Code in a finally block will execute whether or not an exception is thrown.

try
{
    //Code that might throw an exception.
}
catch (Exception ex)
{
    //Code that is only executed if an exception is thrown.
}
finally
{
    //Code that is executed whether or not an exception is thrown.
}

A common situation in which we use a finally clause is to clean up our code. We might do that in situations where we are reading external files, so as not to cause a StackOverflowException or OutOfMemoryException.

In order to read a file from a local device, you must open a stream to the file. The stream remains open as long as we do not explicitly close it. We could use a finally block to close the stream whether or not an exception was thrown:

System.IO.FileStream file = null;
System.IO.FileInfo fileInfo = null;

try
{
    fileInfo = new System.IO.FileInfo("C:\\path\\to\\file.txt");

    file = fileInfo.OpenWrite();
    file.WriteByte(0xF);
}
catch(Exception e)
{
    //Handle the exception
    Console.WriteLine(e.Message);
}
finally
{
    if (file != null)
    {
        file.Close(); //Close the file stream
    }
}

We would do similar things in situations where we are reading from a database or datastore.

Throwing Exceptions

We can also force the C# compiler to throw an exception using the throw keyword.

if(someCondition)
{
    //Normal condition
}
else //Error Condition
{
    throw new Exception("Exception message goes here!");
}

We commonly use this in situations where the program is now in an error state, and we need to stop execution before something worse happens. You might use this in if/else blocks or other decision statements.

One common scenario is to throw a NotImplementedException for methods that do not have their implementation written yet:

public string Method()
{
    throw new NotImplementedException();
}

Note that the above method will not cause a compilation error, despite the fact that the method has a return type and is missing the return keyword.

Custom Exceptions

We can also create our own custom exception classes, such as this one:

public class CustomException : Exception
{
    public CustomException(string message) { /*...*/ }
}

Custom exception classes must inherit from either System.Exception or another class which inherits from System.Exception. Then, because of polymorphism, an instance of our custom exception class can be treated as though it is of type System.Exception.

C# in Simple Terms - Inheritance and Polymorphism
Two of the fundamental object-oriented programming concepts explained! Plus: virtual methods and properties.

We can then throw instances of our custom exception using the throw keyword:

public static void MyMethod()
{
    throw new CustomException("This is a message!");
}

Re-Throwing Exceptions

There may be situations in which our catch block has caught an exception, but needs to throw it so that some code block or try catch block elsewhere in the code can handle it.

There are two ways of doing this. The first is to simply use the throw keyword at the end of the catch block.

try
{
    //Code that might throw an exception
}
catch(Exception ex)
{
    //Handle the exception
    throw;
}

Doing this preserves the original stack trace so that it can be viewed when the exception is caught and handled later.

You can also "re-throw" the exception variable:

try
{
    //Code that might throw an exception
}
catch(Exception ex)
{
    //Handle the exception
    throw ex; //This is different!
}

However, if we do this, the stack trace is reset to start at this line of code; the stack trace that was originally in the exception is lost. Therefore, we normally prefer the throw method over the throw ex method.

Treat Exceptions As Exceptional

The key thing to remember about exceptions is that they are not normal situations and should not be treated as a normal part of the code flow.

For example, we should not throw an exception merely to reach another part of the code (e.g. treating an exception like a decision statement such as if or switch). Exceptions are not decision points.

Also, exceptions should not be used as a return type for methods; they are not intended to be used in this way.

Exceptions are exceptional, and should be treated as such.

Glossary

  • Exception - An unexpected error which occurs while our program is executing.
  • Exception handling - The process by which our code generates and handles exceptions.
  • Thrown - When an exception occurs in our program, we say that the program has "thrown" the exception.
  • Handle - When dealing with exceptions, the code that does something with the thrown exception (e.g. logging it, changing the code that will be executed, etc.) is said to "handle" said exception.

New Keywords

  • try - Specifies a block of code that could throw an exception, where that exception will now be handled.
  • catch - Specifies a kind of exception that will be "caught" and processed.
  • finally - Specifies a code block that will be executed regardless of whether or not an exception was thrown.
  • throw - Throws an exception.

Summary

An exception in C# is an unexpected error encountered at while a program is executing. Exceptions are thrown by the program, and can be caught and processed in try/catch blocks. Optional finally blocks execute code regardless of exceptions thrown. We can throw exceptions explicitly using the throw keyword, and most importantly, exceptions are exceptional and should be treated as such.

This post is a basic introduction to exceptions and exception handling; more details can be found in the official C# documentation.

Handling and throwing exceptions in .NET
Learn how to handle and throw exceptions in .NET. Exceptions are how .NET operations indicate failure to applications.

If you have any questions, please ask them in the comments below and I'd be happy to help clear things up.

In the next part of our C# in Simple Terms series, we will expand on our knowledge of types and type handling by discussing arrays and collections. Check it out here:

C# in Simple Terms - Arrays and Collections
Let’s learn about arrays, lists, ranges, indices, List<T>, Dictionary<TKey, TValue>, Stack<T>, and more!

Happy Coding!