A beginner guide to exceptions
The basics

Today we will take a look at how to propagate errors using Exceptions. I will do my best to keep the article as simple as possible with lots of code samples. I believe that learning the basics is essential in programming.
Prerequisites
You must understand what is a class, a type, a property and have a basic knowledge of inheritance.
What is an Exception?
An exception is a particular Type representing an error. It must derive (inherit) from Exception (directly or indirectly).
You can do two things with exceptions:
throwthem; to create and propagate errors.catchthem; to handle errors.
Error handling
You don’t want your system to crash every time a user sends you an incorrect input, right? Well, let’s start by crashing it anyway!
Throwing Exceptions
Exceptions, when thrown, are propagated down the execution stack (unwinded) until handled (catch-ed).
If they are not, your program will crash (partially or entirely).
Here is a visual representation of this:

Frameworks, SDKs and what not, throws exceptions to alert you, the developer, that an error occurred. Don’t worry; you can throw some too in your own programs!
Here is how:
throw new Exception();
Or, with some custom error message:
throw new Exception("My custom error message that makes sense.");
System exceptions
Now that we saw the Exception class briefly, let’s take a look at other predefined exceptions type, provided by the .NET framework:
NotImplementedExceptionis thrown when an operation is not yet implemented (coded). Often scaffolded by VS, like when you ask VS to implement an interface.NotSupportedExceptionis thrown when a feature is not supported. For example, a provider might not support all features.ArgumentExceptionis thrown when the value of an argument is invalid.ArgumentNullExceptionis thrown when the value of an argument isnull. This is often used by guard clause, guarding injection ofnulldependencies in the constructor. See Dependency injection for more information on the subject.NullReferenceExceptionis thrown when you are using a member of anullobject. For example, in the following code:SomeClass obj = null; obj.SomeProp = 123;, the system will throw aNullReferenceExceptionwhen executing the second statementobj.SomeProp = 123;.IndexOutOfRangeExceptionis thrown when you are accessing a position in an array that does not exist, for example, trying to access the 11th elements of a 10 elements array.ArgumentOutOfRangeExceptionis similar toIndexOutOfRangeExceptionand can be thrown by many collections (and other objects). For example, trying to read the first element of an emptyList<T>will throw this type of exception.
Anatomy of an Exception
Let’s take a look at the most commonly used properties of the Exception class.
For more information, see docs.microsoft.com.
Message
The Message is probably the most used property of the Exception class; it represents the error message.
InnerException
The InnerException is the source of the Exception. You can also see this as the underlying error or as a sub-error.
This property is read-only and must be set by the
Exception(String, Exception)constructor of theExceptionclass.Example:
throw new Exception("My error message", myInnerException);
StackTrace
The StackTrace property can help you diagnose the error and pinpoint its source. This contains the execution stack that causes the error.
Source
The Source property gives you the name of the application or the object that caused the error. This is not always useful but can be.
I more often simply jump to the
StackTraceinstead. The top line is the frame where theExceptionhas been thrown.
Custom exceptions
To create a custom exception, you need to create a class that inherits from another exception.
It is as easy as this:
public class MySuperMeaningfulException : Exception
{
}
By convention, you should suffix your class name with
Exception.
Then you can throw it like any other exception:
throw new MySuperMeaningfulException();
Enforce a message
You can enforce a specific message for a particular type of exception, or allow the caller code to create a custom message by exploiting the base constructors.
Pass-through constructor (custom message):
public class MySuperMeaningfulException : Exception
{
public MySuperMeaningfulException(string message)
: base(message)
{
}
}
// ...
throw new MySuperMeaningfulException("My custom message.");
Enforcing a specific message:
public class MySuperMeaningfulException : Exception
{
public MySuperMeaningfulException()
: base("My custom message.")
{
}
}
// ...
throw new MySuperMeaningfulException();
You can even create a specific message with useful parameters:
public class MySuperMeaningfulException : Exception
{
public MySuperMeaningfulException(int someMeaningfulCount)
: base($"My custom message with a meaningful count of {someMeaningfulCount}.")
{
}
}
// ...
throw new MySuperMeaningfulException(12);
From this point, I will leave your imagination to think of other ways of using custom exception.
Catching exceptions
Throwing errors is exciting and all but what about handling them? To do that, we will use the try and catch keywords.
trywraps the code to be executed; the code that can throw an exception.catchcontains your error handling code.
Here is an example:
try
{
// The code to be executed; that can throw an exception.
}
catch (Exception)
{
// The error handling code.
}
If we take a more in-depth look at the catch block, there is no way to access the Exception instance and most of the time you want access to the exception’s properties (ex.: the error message), let’s see how to fix that:
try
{
// The code to be executed; that can throw an exception.
}
catch (Exception ex)
{
// The ex variable can be used to access the Exception properties.
}
Simple right?
To go a little further, we can add multiple catch blocks that each handles a different type of error. Let’s see how:
try
{
// The code to be executed; that can throw an exception.
}
catch (IndexOutOfRangeException ex)
{
// The IndexOutOfRangeException error handling code.
}
catch (Exception ex)
{
// The other Exception error handling code.
}
Side notes
Depending on the version of C# that you are using, ordering the catch blocks might be necessary; they might need to be ordered from the more precise to the more general exception types. For example,
catch (Exception)should always be the lastcatchblock, when you need it; it is the more general exception of all.Also, the name of the variables might need to be different for each
catchblock.
Tip of the day
If you are creating a system from scratch, creating a custom base exception can be a great idea. For example, in some component of your system, you could handle your custom exceptions and system exceptions differently.
This could be done by two
catchblocks:catch (MyCustomException)andcatch (Exception).Once again, this is an introduction to exceptions; I will leave your imagination to it, for now.
Important
As stated in the official documentation: In general, you should only catch those exceptions that you know how to recover from.
Therefore, if you can only do so much within a catch block, don’t catch it or catch it, do your thing, then rethrow it (see below).
Rethrowing an exception
Sometimes, you want to do something with an Exception then rethrow it, so it continues to “move down” the execution stack.
You can do that in a catch block with throw;.
try
{
// The code to be executed; that can throw a MySuperMeaningfulException.
}
catch (MySuperMeaningfulException ex)
{
logger.Log(ex);
throw;
}
Using throw; like this has the advantage of preserving the exception StackTrace.
You can also catch an exception and throw another exception instead.
try
{
// The code to be executed; that can throw a MySuperMeaningfulException.
}
catch (MySuperMeaningfulException ex)
{
logger.Log(ex);
throw new MyEvenMoreMeaningfulException();
}
Doing that will make MySuperMeaningfulException disappear and be “replaced” by MyEvenMoreMeaningfulException.
But what if you want to preserve MySuperMeaningfulException?
public class MyEvenMoreMeaningfulException : Exception
{
public MyEvenMoreMeaningfulException(Exception innerException)
: base("My even more meaningful exception message.", innerException)
{
}
}
//...
try
{
// The code to be executed; that can throw a MySuperMeaningfulException.
}
catch (MySuperMeaningfulException ex)
{
logger.Log(ex);
throw new MyEvenMoreMeaningfulException(ex);
}
As you may have noticed, the initial MySuperMeaningfulException has become the InnerException value of the MyEvenMoreMeaningfulException.
This is useful to keep a reference to that initial error.
To achieve this result, MyEvenMoreMeaningfulException simply use one of the Exception constructor, passing to it the InnerException.
There is one last throw case that we can cover, which is similar to throw; but it will not preserve the StackTrace of the original error:
try
{
// The code to be executed; that can throw a MySuperMeaningfulException.
}
catch (MySuperMeaningfulException ex)
{
logger.Log(ex);
throw ex;
}
“Plain catch”
You can also create a catch block specifying no type at all.
If you are working with a recent version of C#, this will behave the same way as catch (Exception) but it is not recommended.
try
{
// The code to be executed; that can throw a MySuperMeaningfulException.
}
catch
{
// The error handling code.
}
If you are using an older version of C#, well this is a little different. That said, explaining this is out of the scope of “a beginner guide to exceptions”. The reason why I mentioned it is that you know it exists.
Finally
There is another keyword that we haven’t talked about yet; it is: finally.
The finally keyword allows you to create a block of code that will always be executed, whether the “try” code throws an exception or not.
This is very useful to ensure that IDisposable objects are disposed of correctly.
You can add a finally block after a try block with no catch block or after all your catch blocks.
try/finally example:
try
{
// The code to be executed; that can throw one or more exceptions.
}
finally
{
// The code to be executed whether or not an exception is thrown.
}
try/catch/finally example:
try
{
// The code to be executed; that can throw one or more exceptions.
}
catch (Exception ex)
{
// The error handling code.
}
finally
{
// The code to be executed whether or not an exception is thrown.
}
try/catch/catch/catch/finally example:
try
{
// The code to be executed; that can throw one or more exceptions.
}
catch (MySuperMeaningfulException ex)
{
// MySuperMeaningfulException error handling code.
}
catch (MyEvenMoreMeaningfulException ex)
{
// MyEvenMoreMeaningfulException error handling code.
}
catch (Exception ex)
{
// Some general error handling code.
}
finally
{
// The code to be executed whether or not an exception is thrown.
}
Interesting fact
The compiler does wrap
usingcode blocks intry/finallyblocks. See below for more information.
Under the hood (using)
Since I talked about it, here is an example of a using statement, and it’s IL counterpart.
Here is the using example:
using (var component = new SomeDisposableComponent())
{
component.SomeOperation();
}
Here is the IL (Intermediate language; compiled C# code) version of the same code:
{
SomeDisposableComponent component = new SomeDisposableComponent();
try
{
component.SomeOperation();
}
finally
{
if (component != null)
((IDisposable)component).Dispose();
}
}
Conclusion
Exceptions are very easy to use and are a very powerful mechanism, but they are also more costly than a hand-crafted solution like “operation results”.
Feel free to leave your questions and comments below.
What’s next?
My next article will talk about “operation results” as an alternative way of communicating errors between components.
Until then, happy coding!