Avoid Writing Exception, do this instead in C#.

Rupen Anjaria
4 min readAug 9, 2024

--

We all love the way C# provides us to handle exceptional scenarios using Exceptions. This could be handling null or missing arguments or invalid operations etc. We simply put our code in try-catch-finally block and log the message accordingly. However, misuse of anything good can be more damaging than beneficial. In this article, we will prove that exceptions are costly and explore possible work around of it.

Note: You can try examples shown in this articles using your favorite online C# compiler like ShartLab.io.

What are Exceptions? (briefly)

It’s a feature provided by C# language to deal with any unexpected or exceptional situations that we may encounter during the execution of our program. We wrap our suspected code around try-catch-finally block to leverage it’s benefit. C# not only handles exceptions, but it also performs clean-up afterwards in finally block. Remember, that exception can occur in may places, in your code, in CLR or any third party library we are using.

Why Exceptions are expensive?

When C# finds an exceptions, it performs following steps:

  1. Exception Object Creation: The first step is to create an object that should be able to hold exception details. This includes error message, the stack trace, and the type of exception (e.g. NullReferenceException, IndexOutOfRangeException etc.)
  2. Stack Unwinding: This is where the costly process happens. Runtime begins a process called stack unwinding, where it searches for a try-catch block to handle the exception. It begins from the point where the exception occurred and moves up the call stack/ chain.
  3. Handling the Exception: Here we have two scenarios:
    Try-catch exist: In this case, the control is transferred to the corresponding catch block. The code inside the catch block is executed, effectively handling the exception. If finally block is mentioned then it will be executed to free-up the resources.
    Try-catch doesn’t exist: In this case the exception is considered as unhandled . The runtime continues unwinding the stack until it reaches the entry point of the application. (e.g. main method in console application). After reaching the entry point, it can terminate the application, can show dialog box (for desktop app) or can generate HTTP 500 error in case of ASP.NET application.
    – Note that, without try-catch-finally, we may have resources being marked as busy while they aren’t in reality. Though they will be freed when garbage collector runs it’s cycle, GC doesn’t cover all the scenarios.
  4. Global Exception Handling: If we have configured global handler then it will be invoked to log the error or perform other actions.

As you can see from above points, exception does lots of chores when they are called. This can impact performance of our application significantly if we are throwing exception for non-exceptional scenarios.

Let’s see by an example. Consider below code:

using System;
using System.Collections.Generic;
using System.Diagnostics;

var list = new List<string>();
for (int i = 1; i < 11; i++)
{
if (i % 2 != 0)
{
list.Add(i.ToString());
}
else
{
list.Add(null);
}
}

var watch = new Stopwatch();
watch.Start();

foreach (var s in list)
{
try
{
var i = s.Length;
}
catch (Exception ex)
{
var e = ex.Message;
}
}
watch.Stop();
Console.WriteLine($"Elapsed time with Exception: {watch.ElapsedTicks:N0} ticks");

watch.Reset();
watch.Start();

foreach (var s in list)
{
if(s == null)
{
continue;
}
}
watch.Stop();
Console.WriteLine($"Elapsed time without Exception: {watch.ElapsedTicks:N0} ticks");

Here, we are simply populating a List<string> object with null and odd numbers from 1 to 10. So, it will have items like 1, null, 3, null, 5, null, 7, null, 9, null . After populating list with those values, we are iterating and catching the exception for null values. The other iteration, doesn’t throw/catch exception, but have handled the null scenario gracefully.

Output:

Elapsed time with Exception: 2,494 ticks
Elapsed time without Exception: 67 ticks

The output clearly indicates the cost of Exception.

How to avoid Exceptions?

Now that we have learned that exceptions are costly, let’s look at several alternatives. However, even before that, let me add that I am not suggesting to completely get rid of Exceptions. They add value, however, we should be mindful in using them and should be used when we have truly an exceptional case.

There are two patterns applicable in this case.

  1. Tester-Doer Pattern
    This pattern suggest to perform check for certain cases (tester) and then perform the operation (doer). For example, in our previous demonstration, we have a code like:
foreach (var s in list)
{
try
{
var i = s.Length;
}
catch (Exception ex)
{
var e = ex.Message;
}
}

We can update this as:

foreach (var s in list)
{
if(s == null)
{
continue;
}
}

if (s == null) is nothing but a tester and continue is a doer. Another example would be:

ICollection<int> numbers = ...
...
if (!numbers.IsReadOnly)
{
numbers.Add(1);
}

Here, number is a ReadOnly, so any addition will throw an exception. To overcome, we added a tester if (!numbers.IsReadOnly) and doer numbers.Add(1); .

2. Try-Parse Pattern

This pattern suggests that we should implement method with proper semantics to suggest the outcome. For example, DateTime class has Parse and TryParse method. Parse will throw an exception when an invalid string is provided, while TryParse will return Boolean indicating whether the string is valid for conversion or not. Note that, try-parse suggest to have an alternative version which throws exception.

Another approach would be to create a custom error class and return an instance of that class with error details to the main application. This is primarily useful in case of API response scenario.

Conclusion

While Exception are helpful, throwing them too frequently or cases where we can validate before performing an action, can hamper the performance of the application. The alternatives suggested in this approach can benefit greatly to address the need of an alternative.

--

--

No responses yet