Sample Reflection.Emit code for using exception filters from C#
In this post, I mentioned that one way to use exception filters from C# code is to generate them with Reflection.Emit. Personally I usually prefer static compilation (even post-build assembly merging or rewriting) – there’s really nothing here that necessitates dynamic code generation, but I can understand the desire to avoid complicating the build process. I recently wrote up some code to do this and figured I’d share it here in case others find it useful.
Code Snippet
using System;
using System.Reflection.Emit;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Linq;
namespace FilterTest
{
/// <summary>
/// This class provides some utilities for working with exceptions and exception filters.
/// </summary>
/// <remarks>
/// Code inside of exception filters runs before the stack has been logically unwound, and so the throw point
/// is still visible in tools like debuggers, and backout code from finally blocks has not yet been run.
/// See https://blogs.msdn.com/rmbyers/archive/2008/12/22/getting-good-dumps-when-an-exception-is-thrown.aspx.
/// Filters can also be used to provide more fine-grained control over which exceptions are caught.
///
/// Be aware, however, that filters run at a surprising time - after an exception has occurred but before
/// any finally clause has been run to restore broken invariants for things lexically in scope. This can lead to
/// confusion if you access or manipulate program state from your filter. See this blog entry for details
/// and more specific guidance: https://blogs.msdn.com/clrteam/archive/2009/08/25/the-good-and-the-bad-of-exception-filters.aspx.
///
/// This class relies on Reflection.Emit to generate code which can use filters. If you are willing to add some
/// complexity to your build process, a static technique (like writing in VB and use ILMerge, or rewriting with CCI)
/// may be a better choice (eg. more performant and easier to specialize). Again see
/// https://blogs.msdn.com/rmbyers/archive/2008/12/22/getting-good-dumps-when-an-exception-is-thrown.aspx for details.
/// </remarks>
public static class ExceptionUtils
{
/// <summary>
/// Execute the body with the specified filter.
/// </summary>
/// <param name="body">The code to run inside the "try" block</param>
/// <param name="filter">Called whenever an exception escapes body, passing the exeption object.
/// For exceptions that aren't derived from System.Exception, they'll show up as an instance of RuntimeWrappedException.</param>
/// <param name="handler">Invoked (with the exception) whenever the filter returns true, causes the exception to be swallowed</param>
public static void ExecuteWithFilter(Action body, Func<Exception, bool> filter, Action<Exception> handler)
{
s_filter(body, filter, handler);
}
/// <summary>
/// Execute the body with the specified filter with no handler ever being invoked
/// </summary>
/// <remarks>
/// Note that this allocates a delegate and closure class, a small amount of overhead but something that may not be appropriate
/// for inside of a tight inner loop. If you want to call this on a very hot path, you may want to consider a direct call
/// rather than using an anonymous method.
/// </remarks>
public static void ExecuteWithFilter(Action body, Action<Exception> filter)
{
ExecuteWithFilter(body, (e) => { filter(e); return false; }, null);
}
/// <summary>
/// Execute the body which is not expected to ever throw any exceptions.
/// If an exception does escape body, stop in the debugger if one is attached and then fail-fast.
/// </summary>
public static void ExecuteWithFailfast(Action body)
{
ExecuteWithFilter(body, (e) =>
{
System.Diagnostics.Debugger.Log(10, "ExceptionFilter", "Saw unexpected exception: " + e.ToString());
// Terminate the process with this fatal error
if (System.Environment.Version.Major >= 4)
{
// .NET 4 adds a FailFast overload which takes the exception, usefull for getting good watson buckets
// This will also cause an attached debugger to stop at the throw point, just as if the exception went unhandled.
// Note that this code may be compiled against .NET 2.0 but running in CLR v4, so we want to take advantage of
// this API even if it's not available at compile time, so we use a late-bound call.
typeof(System.Environment).InvokeMember("FailFast",
BindingFlags.Static | BindingFlags.InvokeMethod,
null, null, new object[] { "Unexpected Exception", e });
}
else
{
// The experience isn't quite as nice in CLR v2 and before (no good watson buckets, debugger shows a
// 'FatalExecutionEngineErrorException' at this point), but still deubggable.
System.Environment.FailFast("Exception: " + e.GetType().FullName);
}
return false; // should never be reached
}, null);
}
/// <summary>
/// Like a normal C# Try/Catch but allows one catch block to catch multiple different types of exceptions.
/// </summary>
/// <typeparam name="TExceptionBase">The common base exception type to catch</typeparam>
/// <param name="body">Code to execute inside the try</param>
/// <param name="typesToCatch">All exception types to catch (each of which must be derived from or exactly TExceptionBase)</param>
/// <param name="handler">The catch block to execute when one of the specified exceptions is caught</param>
public static void TryCatchMultiple<TExceptionBase>(Action body, Type[] typesToCatch, Action<TExceptionBase> handler)
where TExceptionBase:Exception
{
// Verify that every type in typesToCatch is a sub-type of TExceptionBase
#if DEBUG
foreach(var tc in typesToCatch)
Debug.Assert(typeof(TExceptionBase).IsAssignableFrom(tc), String.Format("Error: {0} is not a sub-class of {1}",
tc.FullName, typeof(TExceptionBase).FullName));
#endif
ExecuteWithFilter(body, (e) =>
{
// If the thrown exception is a sub-type (including the same time) of at least one of the provided types then
// catch it.
foreach (var catchType in typesToCatch)
if (catchType.IsAssignableFrom(e.GetType()))
return true;
return false;
}, (e) =>
{
handler((TExceptionBase)e);
});
}
/// <summary>
/// A convenience method for when only the base type of 'Exception' is needed.
/// </summary>
public static void TryCatchMultiple(Action body, Type[] typesToCatch, Action<Exception> handler)
{
TryCatchMultiple<Exception>(body, typesToCatch, handler);
}
/// <summary>
/// Set to true to write the generated assembly to disk for debugging purposes (eg. to run peverify and
/// ildasm on in the case of bad codegen).
/// </summary>
private static bool k_debug = false;
/// <summary>
/// Generate a function which has an EH filter
/// </summary>
private static Action<Action, Func<Exception , bool>, Action<Exception>> GenerateFilter()
{
// Create a dynamic assembly with reflection emit
var name = new AssemblyName("DynamicFilter");
AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, k_debug ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run);
ModuleBuilder module;
if (k_debug)
module = assembly.DefineDynamicModule("DynamicFilter", "DynamicFilter.dll");
else
module = assembly.DefineDynamicModule("DynamicFilter");
// Add an assembly attribute that tells the CLR to wrap non-CLS-compliant exceptions in a RuntimeWrappedException object
// (so the cast to Exception in the code will always succeed). C# and VB always do this, C++/CLI doesn't.
assembly.SetCustomAttribute(new CustomAttributeBuilder(
typeof(RuntimeCompatibilityAttribute).GetConstructor(new Type[] { }),
new object[] {},
new PropertyInfo[] { typeof(RuntimeCompatibilityAttribute).GetProperty("WrapNonExceptionThrows") },
new object[] { true }));
// Add an assembly attribute that tells the CLR not to attempt to load PDBs when compiling this assembly
assembly.SetCustomAttribute(new CustomAttributeBuilder(
typeof(DebuggableAttribute).GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) }),
new object[] { DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints } ));
// Create the type and method which will contain the filter
TypeBuilder type = module.DefineType("Filter", TypeAttributes.Class | TypeAttributes.Public);
var argTypes = new Type[] { typeof(Action), typeof(Func<Exception, bool>), typeof(Action<Exception>) };
MethodBuilder meth = type.DefineMethod("InvokeWithFilter", MethodAttributes.Public | MethodAttributes.Static, typeof(void), argTypes);
var il = meth.GetILGenerator();
var exLoc = il.DeclareLocal(typeof(Exception));
// Invoke the body delegate inside the try
il.BeginExceptionBlock();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt, typeof(Action).GetMethod("Invoke"), null);
// Invoke the filter delegate inside the filter block
il.BeginExceptFilterBlock();
il.Emit(OpCodes.Castclass, typeof(Exception));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc_0);
il.EmitCall(OpCodes.Callvirt, typeof(Func<Exception, bool>).GetMethod("Invoke"), null);
// Invoke the handler delegate inside the catch block
il.BeginCatchBlock(null);
il.Emit(OpCodes.Castclass, typeof(Exception));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldloc_0);
il.EmitCall(OpCodes.Callvirt, typeof(Action<Exception>).GetMethod("Invoke"), null);
il.EndExceptionBlock();
il.Emit(OpCodes.Ret);
var bakedType = type.CreateType();
if (k_debug)
assembly.Save("DynamicFilter.dll");
// Construct a delegate to the filter function and return it
var bakedMeth = bakedType.GetMethod("InvokeWithFilter");
var del = Delegate.CreateDelegate(typeof(Action<Action, Func<Exception, bool>, Action<Exception>>), bakedMeth);
return (Action<Action, Func<Exception, bool>, Action<Exception>>)del;
}
// Will get generated (with automatic locking) on first use of this class
private static Action<Action, Func<Exception, bool>, Action<Exception>> s_filter = GenerateFilter();
}
}
There’s also a few helper methods there for common uses of exception filters. For example, to call code that you don’t expect to ever throw an exception you can just wrap it with ExecuteWithFailFast. If any exceptions escape it’ll immediately fail fast with a watson report and minidump (at the point of throw), or if a debugger is attached break at the throw point. The experience is better if running on CLR v4 – it makes use of the new FailFast API that takes an exception (eg. this will cause the debugger to break with the exception assistant, just as if the exception had gone unhandled). For example you can use this as follows:
// FailFast on throw
ExceptionUtils.ExecuteWithFailfast(() =>
{
// Code you don't expect to throw exceptions
throw new ApplicationException("Test unexpected exception");
});
Sometimes it’s useful to be able to catch multiple distinct exception types with the same catch block (without unwinding the stack for other exceptions, so unexpected exceptions are easier to debug live or in a dump file). Here are two examples:
// Catching multiple exception types at once as SystemException
ExceptionUtils.TryCatchMultiple<SystemException>(() =>
{
throw new ArgumentNullException();
},
new Type[] { typeof(InvalidCastException), typeof(ArgumentException), typeof(System.IO.FileNotFoundException) },
(e) =>
{
Console.WriteLine("Caught: " + e.Message);
});
// Catching multiple exception types at once as System.Exception
ExceptionUtils.TryCatchMultiple(() =>
{
throw new ArgumentNullException();
},
new Type[] { typeof(InvalidCastException), typeof(ArgumentException), typeof(System.IO.FileNotFoundException) },
(e) =>
{
Console.WriteLine("Caught: " + e.Message);
});
And here’s an example of a general-purpose exception filter:
// General-purpose filter
ExceptionUtils.ExecuteWithFilter( () => {
Console.WriteLine("In body");
throw new ApplicationException("test");
}, (e) => {
Console.WriteLine("In filter, exception type: {0}", e.GetType().FullName);
return true;
}, (e) => {
Console.WriteLine("In catch, exception type: {0}", e.GetType().FullName);
});
Again, there are good reasons why C# doesn’t support exception filters directly. But there are a few cases where they can be really invaluable. I hope you find this useful.