C# Exceptions – Hidden Gems

The TRY/CATCH/FINALLY exception handling has been around since C# 1.0 and you are very unlikely to find a developer who does not know how to use them. What may not be so obvious is that there are a number of hidden gems that few developers use. This is on top of new enhancements that have been added to C# releases. Below are some of the gems that you may or may not know exist.

Before I list all the gems I want to show you the code that will be used to print out an exception.

public static void DumpException(Exception Ex)
{
    string _message = "Message:";
    do
    {
        Console.WriteLine();
        Console.WriteLine($"{_message} {Ex.Message}");
        Console.WriteLine($"Exception: {Ex.GetType()}");

        if (!string.IsNullOrEmpty(Ex.StackTrace))
            Console.WriteLine($"\nStacktrace:\n{Ex.StackTrace}");

        Console.WriteLine();

        Ex = Ex.InnerException;

        if (Ex != null)
            _message = "Inner Exception Message:";

    } while (Ex != null);
}

This is a simple loop that traverses the Exception and each InnerException, dumping out the error message, exception type and stacktrace to the console.

The test examples I will show below will follow this method calling hierarchy:

Main –> Testn –> GenerateDivByZeroException

Preserving Stack Frames

My first gem is one which I think most people know but definitely one to know if you don’t. Take a look at the code below and then the output.

public static void Test2()
{
    try
    {
        Program.GenerateDivByZeroException();
    }
    catch (Exception ex)
    {
        throw (ex);
    }
}

Notice our stacktrace shows Main and Test2 but no GenerateDivByZeroException call. Once an exception is thrown, part of the information it carries is the stack trace. The stack trace starts with the method that throws the exception and ends with the method that catches the exception. If an exception is re-thrown by specifying the exception in the throw (exception) statement, the stack trace is restarted at the current method and the list of method calls between the original method that threw the exception and the current method is lost. To keep the original stack trace information with the exception, use the throw statement without specifying the exception parameter.

The code below issues a throw without any parameters and preserves the full stacktrace.

public static void Test3()
{
    try
    {
        Program.GenerateDivByZeroException();
    }
    catch (Exception ex)
    {
        throw;
    }
}

Exception Data Dictionary

This is a gem which I have seen few people use. The Exception class has a Data property of IDictionary. This means it can contain key/value pairs. We can use it to provide supplementary data for our exception. Take a look at the code below. First I clear the current dictionary and then add two keypairs.

public static void Test4()
{
    try
    {
        Program.GenerateDivByZeroException();
    }
    catch (Exception ex)
    {
        ex.Data.Clear();
        ex.Data.Add("Key1", "Value1");
        ex.Data.Add("Key2", "Value2");
        throw;
    }
}

We also need to make an amendment to our DumpException method to output the keypairs if any exist.

if (Ex.Data.Count > 0)
{
    Console.WriteLine();
    foreach (DictionaryEntry de in Ex.Data)
        Console.WriteLine($"   {de.Key.ToString()}: \n      {de.Value}");
}

Note: As the exceptions flow up the catch blocks, you are able to add additional keypairs however; do NOT clear the Data property or you risk losing your application debug information.

Custom Exception Class

There are a number of built-in exceptions to throw however; if you have your application throw an exception based on some business logic, you may be tempted to throw one of the built-in-in exceptions like InvalidOperationException. This is rather generic and may not convey what the error is except what message you provide. A much better approach is to use your own custom exception along with the Data Dictionary to convey the error in a more meaningful way. The example below creates a BillingRunException that inherits from System.Exception.

class BillingRunException : System.Exception
{
    public BillingRunException(string Message, int CustomerID, Exception exception) : base(Message, exception)
    {
        this.Data.Clear();
        this.Data.Add("CustomerID", CustomerID.ToString());
    }
}

Next in the catch block we instantiate our custom exception and pass in the current exception with our additional CustomerID parameter. This will preserve the callstack.

public static void Test5()
{
    int CustomerID = 223;
    
    try
    {
        Program.GenerateDivByZeroException();
    }
    catch (Exception ex)
    {
        throw new BillingRunException("Billing calculation error", CustomerID, ex);
    }
}

Our divide by zero exception is now shown as an Inner Exception and our BillingRunException is shown as the outer exception.

Exception Expressions (C#7.0)

One operation that occurs often is testing whether a value is NULL before carrying out an action. Another common occurrence is testing for NULL and then throwing an exception. The following code is an example of setting a string to uppercase.

if (_file == null)
    throw new ArgumentNullException(nameof(_file));

_file = _file.ToUpper();

Starting with C#7.0 they introduced Exception Expressions. We can now carry out the same action in a single line.

_file = (_file ?? throw new ArgumentNullException(nameof(_file))).ToUpper();

Accessing Individual Stackframes

If you take a look at the DumpException method exert below you will notice the StackTrace property is a string will each stack frame separated by a line feed and carriage return. What if you needed to get at each stack frame for additional logging or to suppress certain frames from the output.

if (!string.IsNullOrEmpty(Ex.StackTrace))
    Console.WriteLine($"\n   Stacktrace:\n{Ex.StackTrace}");

Below is the code I have replaced the previous code with to get an array of stack frames and then enumerate each one individually.

if (!string.IsNullOrEmpty(Ex.StackTrace))
{
    Console.WriteLine("\n   Stacktrace:");
    StackTrace _st = new StackTrace(Ex, true);
    foreach (var sf in _st.GetFrames())
    {
        var _aa = sf.GetMethod().ReflectedType.FullName;
        var _bb = sf.GetMethod().Name;
        var _cc = sf.GetFileLineNumber();
        var _dd = sf.GetFileColumnNumber();
        var _ee = sf.GetFileName().Split('\\').Last();
        Console.WriteLine($"   at {_aa}.{_bb}: Line {_cc}: Col {_dd}: File: {_ee}");
    }
}

Here is the output from the Custom Exception class example above with the new stacktrace information.