Sometimes it can be useful to detect if an exception is ongoing.
For instance, in a Dispose()
method, it's valuable to ascertain if an exception is already in progress to avoid throwing a new exception and losing the original one.
Problem encountered
I encountered this issue in a complex scenario within my unit tests. In a mocked Dispose()
method, I aimed to throw an exception if an assertion had failed. However, the problem arose when an exception occurred within a using block of my disposable object, triggering the Dispose()
method. This prevented me from retrieving the original exception in the log during the unit test execution in the CI process.
NOTE: Belong the Microsoft CA1065 recommandation, a
Dispose()
method must not throw an exception.
Below is a simplified example code depicting this scenario:
public class MyCode
{
public void Example()
{
using (var obj = new DisposableObject())
{
var divisor = 0;
var result = 10 / divisor;
}
}
}
public class DisposableObject : IDisposable
{
private bool methodCalled;
public void CallBeforeDispose()
{
this.methodCalled = true;
}
public void Dispose()
{
if (!this.methodCalled)
{
throw new XunitException("The CallBeforeDispose() has not been called.");
}
}
}
As you can see, the Example()
method will throw an exception because we did not call the CallBeforeDispose()
method. In this scenario, an exception is thrown inside the using
block code, leading to the invocation of the Dispose()
method of the DisposableObject
. This invocation will raise another XunitException
exception. Consequently, in this situation, we will lose the original DivideByZeroException
exception.
The Marshal.GetExceptionPointers() method.
To determine if an exception is ongoing, the .NET provides a straightforward static method called Marshal.GetExceptionPointers() which returns an unmanaged pointer to the ongoing exception.
A zero pointer returned by this method indicates that no exception is ongoing.
Conversely, a non-zero pointer returned by the method indicates an ongoing exception.
We can leverage this behavior to detect if an exception is ongoing within our Dispose()
method:
public void Dispose()
{
if (!this.methodCalled)
{
// Do not thrown an exception, if an exception is currently ongoing.
if (Marshal.GetExceptionPointers() == IntPtr.Zero)
{
throw new XunitException("The CallBeforeDispose() has not been called.");
}
}
}
.NET Standard and Marshal.GetExceptionPointers()
If you refer to the table "Applies to" table at the bottom of Marshal.GetExceptionPointers() documentation page, you'll notice that this method is available in .NET Core and .NET Framework libraries but not in the .NET Standard.
To overcome this limitation, we simply employ reflection to access the static GetExceptionPointers()
method within the Marshal
class.
I've created a straightforward helper class that can be utilized in projects targeting both .NET Standard and non-.NET Standard frameworks (especially useful for building libraries intended for various frameworks).
When incorporating the helper into a .NET Standard library, I use expressions to dynamically compile a call, avoiding reflection and enhancing performance when invoking the IsExceptionOnGoing()
method.
using System;
#if NETSTANDARD
using System.Linq.Expressions;
#endif
using System.Runtime.InteropServices;
public static class ExceptionHelper
{
public static bool IsExceptionOnGoing()
{
var ptr = GetExceptionPointers();
if (ptr == IntPtr.Zero)
{
return false;
}
return true;
}
#if NETSTANDARD
private static readonly Func<IntPtr> GetExceptionPointers = BuildGetExceptionPointersFunc();
private static Func<IntPtr> BuildGetExceptionPointersFunc()
{
var memberAccess = Expression.Call(typeof(Marshal).GetMethod("GetExceptionPointers"));
var lambda = Expression.Lambda<Func<IntPtr>>(memberAccess);
return lambda.Compile();
}
#else
private static IntPtr GetExceptionPointers()
{
return Marshal.GetExceptionPointers();
}
#endif
}
A complete example of this helper class is available within one of my public GitHub projects: GillesTourreau/GillesTourreau.AmbientException: Example and helper class to detect ongoing .NET exceptions (github.com)