Throwing Exceptions in .NET

Posted by AgileCoder on June 5, 2020

Two of the most common exception handling anti-patterns I see that make troubleshooting and debugging .NET applications more difficult are:

  1. Catching an exception, logging it and then re-throwing it in a way that destroys the stack trace.
  2. Throwing System.Exception, System.ApplicationException or System.SystemException rather than a more specific exception.

Doing either of these will cause troubleshooting your code to be more difficult and will result in your code failing code review by static code analysis engines like FxCop, Gendarme, or SonarQube.

Do Not Destroy the Stack Trace

An easy mistake nearly every C# or VB.Net developer has made is to catch an exception, do something with it like log the details, and then throw the exception in a way that forces the CLR to treat it as a new exception. This destroys any inner information that may have come from outside the Catch block, including clearing the stack trace.

Instead, developers should re-throw the existing exception in a way that keeps the stack trace intact. Some examples:

C# Bad Example:

try
{
    //some code here that causes an exception to be thrown
}
catch (Exception ex)
{
    log.Error(ex);
    throw ex; // no info from outside the catch block, no trace
}

C# Good example:

try
{
    //some code here that causes an exception to be thrown:
}
catch (Exception ex)
{
    log.Error(ex);
    throw; // preserves the stack trace
}

VB.Net Bad Example:

Try
    'some code here that causes an exception to be thrown
Catch ex As Exception
    log.Error(ex)
    Throw ex ' no info from outside the catch block, no trace
End Try

VB.Net Good example:

Try
    'some code here that causes an exception to be thrown
Catch ex As Exception
    log.Error(ex)
    Throw ' preserves the stack trace
End Try

 

Do Not Throw Generic System Exceptions

When we throw System.Exception, System.ApplicationException or System.SystemException we are missing a chance to communicate more detailed troubleshooting information to any developer who comes after us. It is preferred to use the more specific exception types that derive from these two types. When you throw the more specific exception, the type itself is a clue to the problem. In addition, as shown in the examples below, the more specific exceptions are typically overloaded to allow multiple parameters. This allows you to pass relevant troubleshooting details up the call stack.

C# Bad Example:

public void Add(string Value)
{
    if (String.IsNullOrWhitespace(Value))
	{
        throw new Exception();
    }
    // code that operates on Value
}

C# Good Example:

public void Add(string Value)
{
    if (String.IsNullOrWhitespace(Value))
	{
        throw new ArgumentException("Item cannot be null or whitespace.", "Value");
    }
    // code that operates on value
}

VB.Net Bad Example:

Public Sub Add(Value As String)
    If String.IsNullOrWhitespace(Value) Then
        Throw New Exception()
    End If
    ' code that operates on value
End Sub

VB.Net GoodExample:

Public Sub Add(Value As String)
    If String.IsNullOrWhitespace(Value) Then
        Throw New ArgumentException("Item cannot be null or whitespace.", "Value")
    End If
    ' code that operates on value
End Sub

 

Available Exceptions

Below is a list of common exceptions that can be thrown, or you can inherit from one of the generic types and create your own custom exception.

System.ArgumentException
    System.ArgumentNullException
    System.ArgumentOutOfRangeException
    System.DuplicateWaitObjectException
System.ArithmeticException
    System.DivideByZeroException
    System.OverflowException
    System.NotFiniteNumberException System.ArrayTypeMismatchException
System.ExecutionEngineException
System.FormatException
System.InvalidCastException
System.InvalidOperationException
    System.ObjectDisposedException
System.InvalidProgramException
System.IO.IOException
    System.IO.DirectoryNotFoundException
    System.IO.EndOfStreamException
    System.IO.FileLoadException
    System.IO.FileNotFoundException
    System.IO.PathTooLongException
System.NotImplementedException
System.NotSupportedException
System.OutOfMemoryException
System.RankException
System.Security.SecurityException
System.Security.VerificationException
System.StackOverflowException
System.Threading.SynchronizationLockException
System.Threading.ThreadAbortException
System.Threading.ThreadStateException
System.TypeInitializationException
System.UnauthorizedAccessException

If you would like to see a list of all of the exceptions available in the assemblies referenced in your project:

  1. Open your project or solution in Visual Studio
  2. Press CTRL + ALT + E to open the Exception Settings window
  3. Expand the Common Language Runtime Exceptions item in the list