Understanding the Exception.HResult property in C#

Since launching support for showing parsed HRESULT on the elmah.io UI, we have received a few questions about HRESULTs in general. While I understand that HRESULTs is probably not on everyone's radar, I think a general understanding will help with debugging. In this post, I'll share the things I know about HRESULTs.

So, what are HRESULTs and why should you care? Let's set the scene by discussing something everyone knows: Exceptions! We have all dealt with exceptions in some way or another. Exception handling is a key aspect of any software development with C#. It allows us to manage when errors happen and prevent applications from crashing. The try-catch block in C# is available to catch when an exception happens in the code inside the try block. Exceptions carry more information than just error messages and stack traces. Some exceptions embed context-specific information about what went wrong. Like the ParamName property on ArgumentException that indicates which argument is causing the error.

A shared property that all exceptions have is named HResult. HResult is C#'s way of representing HRESULTs which is short for "Handle to result" or sometimes referenced as "Result handle". A HRESULT is a standardized way of communicating error information across different components and layers of the system. It is a 32-bit integer that contains three pieces of information: severity, facility, and a code (typically an error code). All exceptions are assigned an HRESULT, no matter if it is caused by managed or unmanaged code. In this way, the HResult property adds an extra layer of details to exceptions.

If you are old like me, you remember the Component Object Model (COM) era. The concept of RESULT is from these days and serves as the primary mechanism for error handling and communication between COM components. While only a few of us still have to deal directly with COM, the HResult property remains relevant.

Let's find out why by looking at a simple example:

In the code (inside the catchy named ConsoleApp23 application), I intentionally try to divide an integer with 0, causing a DivideByZeroException. In real life, you would never want to do anything like this and you should always try to avoid exceptions in the first place (more info here). But the failing code lets us inspect the thrown exception in the debugger. Besides the properties that you already know like Message and StackTrace, there's the HResult property with a value of -2147352558. I understand if you don't exactly feel more informed by looking at -2147352558. The value is a decimal representation of the hex value but can be easily converted to hex:

var hex = $"0x{e.HResult:X8}";
// hex is 0x80020012

In the case of the DivideByZeroException the HResult is 0x80020012. We can break up the hex value like this:

Segment Description
0x8 Severity indicating failure. 0x8 means failure and 0x0 means not.
002 (0x2 in hex) Facility code indicating the source of the error.
0012 (0x12 in hex) Error code within the specified facility.

In this example 0x8 indicates that this is a failure, 0x2 that it is from facility 2, and with error code 18 (open the calculator and convert hex 0x12 to decimal if this looks strange to you). We can parse these values in C# too, using the following code:

var isFailure = (e.HResult & 0x80000000) != 0; // isFailure is true
var facility = (e.HResult & 0x7FFF0000) >> 16; // facility is 2
var code = (e.HResult & 0xFFFF); // code is 18

Unless you have a photographic memory, you may need to look up what facility 2 and error code 18 means. Microsoft has a range of various pages listing possible HRESULTs which can make it a bit hard. Luckily, there is a website named hresult.info that tries to collect all possible HRESULTs in a single database. When looking up 0x80020012 we will see the following:

As shown from the screenshot, the HRESULT is from the facility named FACILITY_DISPATCH and the error code is DISP_E_DIVBYZERO.

Understanding each of these parts enables you to decipher the HRESULT and will in some instances give you additional insight in the underlying cause of an exception. Let's look at a more advanced example than the DivideByZeroException:

try
{
    Activator.CreateInstance(Type.GetTypeFromCLSID(Guid.NewGuid()));
}
catch (COMException e)
{
    var isFailure = (e.HResult & 0x80000000) != 0; // isFailure is true
    var facility = (e.HResult & 0x7FFF0000) >> 16; // facility is 4
    var code = (e.HResult & 0xFFFF); // code is 340
}

In this code, I'm using the System.Activator class to try and create an instance of a class with a non-existing class identifier (CLSID). This will cause an exception of type COMException. If looking up the HRESULT we see that the facility is FACILITY_ITF and the error code is E_CLASSNOTREG. So, in this case, the HRESULT tells us exactly what went wrong, rather the only having the COMException available. A COMException can cover a lot of various errors.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

Before we continue, I want to list a range of HRESULTs that you may have already seen or definitely will notice after having read this post:

  • 0x80004005 (E_FAIL): Indicates a general failure or unspecified error condition.
  • 0x80070002 (E_FILENOTFOUND): Denotes that the specified file or directory could not be found.
  • 0x80070005 (E_ACCESSDENIED): Signifies that access to a resource or operation is denied due to insufficient permissions.
  • 0x8007007B (E_INVALID_NAME): Indicates that the specified file name, directory name, or path syntax is invalid.
  • 0x80070057 (E_INVALIDARG): Denotes that one or more arguments passed to a method or function are invalid.

While all exceptions from the .NET framework have fixed and somewhat documented HRESULTs, you may encounter HRESULTs that you cannot find documentation about online. Facilities and error codes are not a fixed list so developers can make up their own for their applications. You will need to look up application-specific documentation to decipher results like these.

Now that we understand HRESULTs we can start integrating logic into our exception-handling code by including the when keyword:

try
{
    Activator.CreateInstance(Type.GetTypeFromCLSID(Guid.NewGuid()));
}
catch (COMException e) when ((e.HResult & 0x7FFF0000) >> 16 == 4)
{
}

This code only catches the COMException if the facility is 4 (FACILITY_ITF). I'm not sure if anyone would ever want to do this, but it shows how to implement fine granular exception handling using HRESULTs.

As already suggested by the examples above, some exceptions often requires you to look at the HRESULT while others don't. In the case of the DivideByZeroException we already had all the info we needed before starting to parse the HRESULT. In the Interop scenario, the HRESULT provided helpful information. In general, you should at least consider HRESULT in the following three scenarios:

  1. Interop Scenarios: HResult is particularly useful when dealing with interoperation between managed and unmanaged code, such as COM interop or platform-specific APIs.
  2. Low-Level System Programming: When working with low-level system programming or accessing system APIs directly, HResult allows you to obtain detailed error information.
  3. Custom Error Conditions: For application-specific error conditions that cannot be adequately represented by standard exception types, developers can define custom HResult values to convey specific error scenarios accurately.

For managed code that most of us are writing using C#, HRESULTs are often not required since exception messages are already quite good in .NET.

Tools

We have already gone through a range of different tools able to show and process HRESULTs. Here's a list of both commercial and free tools:

  1. Visual Studio: Probably the first place you will encounter HRESULTs is the Visual Studio debugger that I already showed you. The debugger will show you the decimal representation of the HResult property on all exceptions.
  2. Exception Visualizer extension for Visual Studio: This is an extension I made for Visual Studio, that not only shows parsed HRESULTs but also tries to improve the way Visual Studio shows exceptions while debugging:
  1. hresult.info: As we've already seen, hresult.info is an online service able to look at HRESULTs and present various information. The amount of HRESULTs there is really impressive.
  2. elmah.io: Sorry for the self-promotion here, but I still want to mention elmah.io. When logging exception in elmah.io we parse and show the HRESULT directly in the UI:
  1. Elmah.Io.HResults: Finally, I want to highlight one of our open-source libraries. The parsed result as you see on the screenshot above, is coming from the Elmah.Io.HResults NuGet package that we provide for anyone wanting to better understand HRESULTs.

You will also encounter HRESULTs when using both WinDbg and the SysInternals tools. I won't cover these tools in this blog post but encourage you to check them out.

I hope that HRESULTs make a lot more sense now. While not being a subject that everyone is interested in, HRESULTs can play a crucial role in debugging exceptions.