C# Exception Handling Best Practices

By: mwatson
  |  February 28, 2024
C# Exception Handling Best Practices

Welcome to Stackify’s guide to C# exception handling. In this post, we cover the following topics:

  • Basics about C# Exceptions, including examples
  • Common .NET Exceptions
  • How to Create Your Own Custom C# Exception Types
  • How to Find Hidden .NET Exceptions
  • C# Exception Logging Best Practices
  • Why is this topic so important

In modern languages like C#, problems are typically modeled using exceptions. Jeff Atwood (of StackOverflow fame) once called exceptions “the bread and butter of modern programming languages.” That should give you an idea of how important this construct is.

Regrettably, booming software developers overlook a crucial subject in their education – effectively managing errors and other code-related issues. In my personal experience, this topic receives insufficient attention.

Tutorials and lessons showcasing sample application development too often skip how you manage exceptions, omitting this step for brevity’s sake.

And this is a shame, since we could argue that software development is all about handling errors and managing exceptions. When you prioritize brevity, too many programming students become programming professionals who lack an understanding of real-world exception-handling mechanisms.

This post is our humble contribution to changing that scenario. Let’s dig in.

What is an Exception?

Exceptions are a type of error that occurs during the execution of an application. Typically, problems that are not anticipated result in errors, whereas the application’s code intentionally foresees exceptions due to various reasons.

Applications use exception handling logic to explicitly handle the exceptions when they happen. Exceptions can occur for a wide variety of reasons. From the infamous NullReferenceException to a database query timeout.

The Anatomy of C# Exceptions

Exceptions allow an application to transfer control from one part of the code to another. When an exception occurs, it disrupts the ongoing code flow and returns control to a parent try-catch block. In C#, you handle exceptions using the following keywords:

  • try – A try block encloses a section of code. When code throws an exception within this block, the corresponding catch handles the exception
  • catch – When an exception happens, the code within the Catch block executes. This is where you are able to handle the exception, log it, or ignore it
  • finally – The finally block enables the execution of specific code irrespective of exception. For instance, it facilitates the disposal of an object that requires disposal
  • throw – The throw keyword crafts a fresh exception, ascending to a try-catch-finally block

Example #1: The Basic “try catch finally” Block

The C# try and catch keywords help establish a try-catch block, which surrounds code capable of throwing exceptions. This block serves to manage potential exceptions that might arise during execution.

In case an exception occurs, this try-catch block will manage the exception, which guarantees the application avoids triggering an unhandled exception, user errors, or application crash.

Here, we present a straightforward instance of a method that might raise an exception. We’ll delve into the correct approach of employing a try-catch-finally construct to manage errors effectively.

WebClient wc = null;
try
{
wc = new WebClient(); //downloading a web page
var resultData = wc.DownloadString("http://google.com");
}
catch (ArgumentNullException ex)
{
//code specifically for a ArgumentNullException
}
catch (WebException ex)
{
//code specifically for a WebException
}
catch (Exception ex)
{
//code for any other type of exception
}
finally
{
//call this if exception occurs or not
//in this example, dispose the WebClient
wc?.Dispose();
}

Your exception handling code can utilize multiple C# catch statements for different types of exceptions, which can be very useful depending on what your code is doing.

In the previous example, ArgumentNullException occurs only when the website URL passed in is null. A WebException is caused by a wide array of issues. Catching specific types of exceptions can help tailor how to handle them.

Example #2: Exception Filters

Exception filters introduced in C# 6 enable you to have even more control over your catch blocks and further tailor how you handle specific exceptions. These features help you fine-tune exactly how you handle exceptions and which ones you want to catch.

Before C# 6, you would have had to catch all types of WebException and handle them. You can now select to manage them only in specific situations and allow different situations to rise to the calling code. Here is a modified example with filters:

WebClient wc = null;
try
{
wc = new WebClient(); //downloading a web page
var resultData = wc.DownloadString("http://google.com");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.ProtocolError)
{
//code specifically for a WebException ProtocolError
}
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound)
{
//code specifically for a WebException NotFound
}
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.InternalServerError)
{
//code specifically for a WebException InternalServerError
}
finally
{
//call this if exception occurs or not
wc?.Dispose();
}

Common .NET Exceptions

Proper exception handling is critical to all application code. In the programming world, we often rely on numerous common exceptions that see frequent use. The most common being the dreaded null reference exception. Below are some of the common C# Exception types that you will see regularly.

The follow is a list of common .NET exceptions:

  • System.NullReferenceException – Very common exception related to calling a method on a null object reference
  • System.IndexOutOfRangeException – Occurs attempting to access an array element that does not exist
  • System.IO.IOException – Used around many file I/O type operations
  • System.Net.WebException – Commonly thrown around any errors performing HTTP calls
  • System.Data.SqlClient.SqlException – Various types of SQL Server exceptions
  • System.StackOverflowException – If a method calls itself recursively, you may get this exception
  • System.OutOfMemoryException – If your app runs out of memory
  • System.InvalidCastException – When attempting to convert an object to an incompatible type
  • System.InvalidOperationException – Common generic exception in a lot of libraries
  • System.ObjectDisposedException – Attempting to utilize an object that has already undergone disposal

How to Create Your Own C# Custom Exception Types

C# exceptions, akin to any other C# object, take the form of classes. All exceptions inherit from a base System.Exception class. You can use  many common exceptions within your own code. Commonly, developers use the generic ApplicationException or Exception object to throw custom exceptions. However, you can also create your own type of exceptions.

Crafting your unique C# custom exceptions are most beneficial when you intend to catch a particular exception type and manage it distinctively. Custom exceptions can also be helpful to track a very specific type of exception that you deem extremely critical. You can create a custom exception type to monitor application errors, then use an error monitoring tool to keep track of the logs.

At Stackify by Netreo, we have created a few custom exception types. One good example is a ClientBillingException. Billing is something we don’t want to mess up. And if it does happen, we want to prioritize handling those exceptions.

Using a custom exception type, we can write special code to handle that exception. We can also monitor our application for that specific type of exception and notify the on-call person when it happens.

Benefits of custom C# Exception types:

  • Calling code can do custom handling of the custom exception type
  • Ability to do custom monitoring around that custom exception type

Here is a simple example from our code:

public void DoBilling(int clientID)
{
Client client = _clientDataAccessObject.GetById(clientID);

if (client == null)
{
throw new ClientBillingException(string.Format("Unable to find a client by id {0}", clientID));
}
}

public class ClientBillingException : Exception
{
public ClientBillingException(string message)
: base(message)
{
}
}

How to Find Hidden .NET Exceptions

What Are First Chance Exceptions?

Experiencing numerous exceptions is common; we often throw, catch and subsequently disregard them. The .NET Framework’s internals also throw exceptions that people simply discard. One of the features of C# is something called first chance exceptions, which gives you visibility into every single .NET Exception being thrown.

The usage of code like the one below is widespread in applications. This code can throw thousands of exceptions a minute and nobody would ever know it. This code is from another blog post about an app that had serious performance problems due to bad exception handling.

If the reader is null, exceptions will occur.

If columnName is null, exceptions will occur.

If columnName does not exist in the results, exceptions will occur.

If the value for the column is null, exceptions will occur.

If the value is not a proper DateTime, exceptions will occur.

public DateTime? GetDate(SqlDataReader reader, string columnName)

{
DateTime? value = null;
try
{
value = DateTime.Parse(reader[columnName].ToString());
}
catch
{
}
return value;
}

How to Enable First Chance Exceptions with Visual Studio

When you run your application in Visual Studio, the debugger is running. You can make Visual Studio pause if a C# Exception happens, which can help you find exceptions in your code that you did not know existed.

To access Exception Settings, go to Debug -> Windows -> Exception Settings

Under “Common Language Runtime Exceptions” you can select the exceptions you want the debugger to break for automatically. We would suggest just toggling the checkbox for all. When you break for an exception, you can instruct the system to ignore that specific type of exception,if you want to exclude it.

How to View All Exceptions with Prefix

Prefix, Stackify by Netreo’s .NET profiler, can also show you all of your exceptions and more. Check out the latest on Prefix or start your free trial today!

Stackify by Netreo’s Retrace solution for your servers can also collect all first chance exceptions via the .NET profiler. Without any code or config changes, Retrace will automatically collect and show you all of your exceptions.

How to Subscribe to First Chance Exceptions in Your Code

The .NET Framework provides a way to subscribe to an event to get a callback anytime an Exception occurs. You could use this feature to capture all of the exceptions in your apps and output exceptions to your Debug window for easy visibility without cluttering your log files.

When your console app first starts, you only want to do this once in the Main() method. Or, when an ASP.NET web app starts, you only want to do this once at startup.

AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
Debug.WriteLine(eventArgs.Exception.ToString());
};

C# Exception Logging Best Practices

Proper exception handling is critical for any application. A key part of this process is using a logging library, where you can log exceptions and keep a record of any that occur. Please check out our guide to C# Logging Best Practices to learn more on this subject.

You can also log your exceptions using NLog, Serilog or log4net. All three frameworks give you the ability to log your exceptions to a file. Each framework enables you to send your logs to various other targets, like a database, Windows Event Viewer, email or an error monitoring service.

Exceptions play a critical role in identifying issues within your code, so logging every exception in your application is crucial.

Logging more contextual details helps with troubleshooting an exceptions,knowing the specific customer involved and understanding the key variables in use.

try
{
//do something
}
catch (Exception ex)
{
//LOG IT!!!
Log.Error(string.Format("Excellent description goes here about the exception. Happened for client {0}", _clientContext.ClientId), ex);
throw; //can rethrow the error to allow it to bubble up, or not, and ignore it.
}

To learn more about logging contextual data, read this: What is structured logging and why developers need it

Why Logging Exceptions to a File Is Not Enough

Logging your exceptions to a file is a good best practice. However, this is not enough once your application is running in production. If you don’t log into each server every day and check the log files, you won’t know the exceptions happen. That file becomes a black hole.

An error monitoring service is a key tool for any development team that allows you to collect all of your exceptions in a central location.

  • Centralized exception logging
  • View and search all exceptions across all servers and applications
  • Uniquely identify exceptions
  • Receive email alerts on new exceptions or high error rates

C# Exception Handling: What Comes Next?

Error management does not receive enough attention in software development education. This lack of focus is surprising, since handling errors is a basic concern when you write applications.

C#, being a modern language, uses exceptions for handling problematic scenarios in the code. So, learning proper exception handling is essential when you use that language.

In this post, we’ve offered a comprehensive guide on C# exception handling, covered the basics, like what exceptions are and why they matter, and explored more advanced topics, specifically those related to C#.

What should be your next step? For one, don’t stop studying. There is plenty of useful content on exceptions and related topics around the web. As a C# developer, you might find the Microsoft technical documentation portal particularly useful.

Also, keep reading the Stackify blog, where we often publish content on various tech topics that might be useful for you.

Finally, leverage the power of tools available to you, including Prefix to monitor all exceptions happening in your web app, as they happen. Besides C#/.NET, Prefix is also available for Java. Download and try Prefix today.

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More

Want to contribute to the Stackify blog?

If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]