I often need a simple exception with a meaningful name and some helpful message which usually just looks like this one:
public class BackgroundImageNotFoundException : Exception { public BackgroundImageNotFoundException(string fileName) : base($"Where is the '{fileName}' image?") { } }
Then I end up creating a bunch of similar classes that mostly differ only by name. I thought there must be some more efficient way to do this so I've been experimenting with a new idea that I call the DynamicException
. It is a special exception that gets compiled at runtime and contains only the most basic properties.
At the bottom of it is the main DynamicExceptionFactory
class which creates a new excption type at runtime. Such exceptions have the specified name, message and/or inner exception:
public interface IDynamicExceptionFactory
{
[NotNull, ContractAnnotation("name: null => halt")]
Exception CreateDynamicException([NotNull] string name, [CanBeNull] string message, [CanBeNull] Exception innerException);
}
internal class DynamicExceptionFactory : IDynamicExceptionFactory
{
public static IDynamicExceptionFactory Default { get; } = new DynamicExceptionFactory();
public Exception CreateDynamicException(string name, string message, Exception innerException)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (!name.EndsWith(nameof(Exception))) throw new ArgumentException(paramName: nameof(name), message: $"Exception name must end with '{nameof(Exception)}'.");
var baseType = typeof(DynamicException);
var baseConstructorParameterTypes = new[] { typeof(string), typeof(Exception) };
var baseConstructor = baseType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, baseConstructorParameterTypes, null);
var assemblyName = new AssemblyName($"DynamicAssembly_{Guid.NewGuid():N}");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
var typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public);
typeBuilder.SetParent(typeof(DynamicException));
// Create a constructor with the same number of parameters as the base constructor.
var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, baseConstructorParameterTypes);
var ilGenerator = constructor.GetILGenerator();
// Generate constructor code
ilGenerator.Emit(OpCodes.Ldarg_0); // push 'this' onto stack.
ilGenerator.Emit(OpCodes.Ldarg_1); // push 'message' onto stack.
ilGenerator.Emit(OpCodes.Ldarg_2); // push 'innerException' onto stack.
ilGenerator.Emit(OpCodes.Call, baseConstructor); // call base constructor
ilGenerator.Emit(OpCodes.Nop); // C# compiler add 2 NOPS, so
ilGenerator.Emit(OpCodes.Nop); // we'll add them, too.
ilGenerator.Emit(OpCodes.Ret); // Return
var dynamicExceptionType = typeBuilder.CreateType();
return (Exception)Activator.CreateInstance(dynamicExceptionType, message, innerException);
}
}
Custom exceptions are inherited from a DynamicException
so that they can be caught and filtered.
public abstract class DynamicException : Exception
{
protected DynamicException(string message, Exception innerException)
: base(message, innerException) { }
[NotNull]
public static IDynamicExceptionFactory Factory => DynamicExceptionFactory.Default;
}
In order to make the exception creation really simple, I use a few helper extensions for tuples that I use to specify some or all of the three parameters:
public static class DynamicExceptionFactoryExtensions
{
/// <summary>
/// Creates a DynamicException from the specified template.
/// </summary>
[NotNull, ContractAnnotation("factory: null => halt; template: null => halt")]
public static Exception CreateDynamicException([NotNull] this IDynamicExceptionFactory factory, [NotNull] IDynamicExceptionTemplate template)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (template == null) throw new ArgumentNullException(nameof(template));
return factory.CreateDynamicException(template.Name(), template.Message, template.InnerException);
}
/// <summary>
/// Creates a DynamicException with the name of the calling method, and with the specified message and optionally an inner exception.
/// </summary>
/// <returns></returns>
[NotNull, ContractAnnotation("factory: null => halt")]
public static Exception CreateDynamicException([NotNull] this IDynamicExceptionFactory factory, string message, Exception innerException = null, [CallerMemberName] string memberName = null)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
return factory.CreateDynamicException($"{memberName}{nameof(Exception)}", message, innerException);
}
public static Exception ToDynamicException(this (string Name, string Message) template)
{
return DynamicException.Factory.CreateDynamicException(template.Name, template.Message, null);
}
public static Exception ToDynamicException(this (Enum ErrorCode, string Message) template)
{
return DynamicException.Factory.CreateDynamicException(template.ErrorCode.ToString(), template.Message, null);
}
public static Exception ToDynamicException(this (string Name, string Message, Exception InnerException) template)
{
return DynamicException.Factory.CreateDynamicException(template.Name, template.Message, template.InnerException);
}
public static Exception ToDynamicException(this (Enum ErrorCode, string Message, Exception InnerException) template)
{
return DynamicException.Factory.CreateDynamicException(template.ErrorCode.ToString(), template.Message, template.InnerException);
}
}
Exception filtering is supported by another set of extensions to check the exception name:
public static class DynamicExceptionExtensions
{
[ContractAnnotation("ex: null => halt; name: null => halt")]
public static bool NameEquals([NotNull] this DynamicException ex, [NotNull] string name, StringComparison comparisonType = StringComparison.Ordinal)
{
if (ex == null) throw new ArgumentNullException(nameof(ex));
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
return ex.GetType().Name.Equals(name, comparisonType);
}
[ContractAnnotation("ex: null => halt")]
public static bool NameEquals([NotNull] this DynamicException ex, Enum errorCode, StringComparison comparisonType = StringComparison.Ordinal)
{
if (ex == null) throw new ArgumentNullException(nameof(ex));
return ex.NameEquals(errorCode.ToString(), comparisonType);
}
[ContractAnnotation("ex: null => halt")]
public static bool CreatedFrom<T>([NotNull] this DynamicException ex, StringComparison comparisonType = StringComparison.Ordinal) where T : IDynamicExceptionTemplate
{
if (ex == null) throw new ArgumentNullException(nameof(ex));
return ex.NameEquals(Regex.Replace(typeof(T).Name, "Template$", string.Empty), comparisonType);
}
}
For exceptions that can be used more than once there is an option to create an exception template:
public interface IDynamicExceptionTemplate
{
[CanBeNull]
string Message { get; }
[CanBeNull]
Exception InnerException { get; }
}
public abstract class DynamicExceptionTemplate : IDynamicExceptionTemplate
{
public abstract string Message { get; }
public Exception InnerException { get; set; }
[NotNull, ContractAnnotation("template: null => halt")]
public static implicit operator Exception([NotNull] DynamicExceptionTemplate template)
{
if (template == null) throw new ArgumentNullException(nameof(template));
return template.ToDynamicException();
}
}
public static class DynamicExceptionTemplateExtensions
{
public static string Name<T>(this T template) where T : IDynamicExceptionTemplate
{
return Regex.Replace(typeof(T).Name, $"({nameof(Exception)})?Template$", nameof(Exception));
}
public static Exception ToDynamicException(this IDynamicExceptionTemplate template)
{
return DynamicException.Factory.CreateDynamicException(template.Name(), template.Message, template.InnerException);
}
}
Such a template contains only a message that is rendered from custom properties (if any) and the name of the exception is derived form the template's name.
Example
Here's an example on how I use this:
throw ("SettingNotFoundException", $"Setting {fullName.ToString().QuoteWith("'")} not found.").ToDynamicException());
If I wanted to use it in more than one place I could create a template like:
public class SettingNotFoundExceptionTemplate : DynamicExceptionTemplate
{
public override string Message => $"Setting {SettingName.QuoteWith("'")} not found.";
public string SettingName { get; set; }
}
and throw it with:
throw new SettingNotFoundExceptionTemplate
{
SettingName = fullName.ToString()
}
.ToDynamicException();
In tests I can check if it gets thrown with:
Assert.That.ThrowsExceptionFiltered<DynamicException>(() =>
{
// code that throws a SettingNotFoundException
},
ex => ex.NameEquals("SettingNotFoundException"));
A templated exception could be filtered with:
ex.CreatedFrom<SettingNotFoundExceptionTemplate>()
where ThrowsExceptionFiltered
is my Assert
extension allowing exception filtering:
public static T ThrowsExceptionFiltered<T>(this Assert assert, Action action, Func<T, bool> filter = null) where T : Exception
{
filter = filter ?? (ex => true);
try
{
action();
Assert.Fail($"Expected exception {typeof(T).Name.QuoteWith("'")}, but none was thrown.");
}
catch (T ex) when (filter(ex))
{
return ex;
}
catch (Exception ex)
{
Assert.Fail($"Expected exception '{typeof(T).Name}', but {ex.GetType().Namespace.QuoteWith("'")} was thrown.");
}
// This is only to satisfy the compiler. We'll never reach to this as it'll always fail or return earlier.
return null;
}
CustomException
when you log it because it callsex.ToString()
so you'll always have to look for the enum property to know what went wrong. What I want is to have the reason for the exception where it belongs which is the name of the exception likeSettingNullException
rather thanCustomException.Error = "SettingNull"
. \$\endgroup\$DynamicException
which would have some kind of read-only property (string ExceptionKind
)? \$\endgroup\$