Edit: this approach may not work for handling exceptions
Upon reflection, the solution I describe below may not work for catching and handling exceptions, at least not without some additional effort. The problem is that if you throw an exception from within the scope of a using
statement, and you do not catch the exception within the scope of the using
statement (e.g. it is caught further up in the call stack), the using
statement will go out of scope before the exception is caught. Thus, the Dispose
method in ExceptionManagerContext
will be called, and the context popped from the stack, before the exception thrown within the using statement is handled by your exception manager.
Here is your class that catches and logs or otherwise handles exceptions. You can add a Stack
of IExceptionManagerContext
objects to it, so that you can Push
and Pop
contexts.
class ExceptionManager
{
private Stack<IExceptionManagerContext> contexts;
public ExceptionManager()
{
contexts = new Stack<IExceptionManagerContext>();
PushContext(new DefaultExceptionManagerContext());
}
public void PushContext(IExceptionManagerContext context)
{
contexts.Push(context);
}
public void PopContext()
{
contexts.Pop();
}
private IExceptionManagerContext CurrentContext
{
get { return contexts.Peek(); }
}
public void Handle(Exception ex)
{
if (CurrentContext.EnableLogging)
{
Log(ex);
}
else
{
DoSomethingElseWith(ex);
}
}
}
The default context at the bottom of the stack can implement some default options, such as enabling logging. You could add any number of other properties to IExceptionManagerContext
to make your exception management configurable.
interface IExceptionManagerContext
{
bool EnableLogging { get; }
}
class DefaultExceptionManagerContext : IExceptionManagerContext
{
public bool EnableLogging { get { return true; } }
}
A custom context class implements IDisposable
so that it can safely push and pop itself on/off the stack of contexts in the exception manager. This class will need some sort of reference to the exception manager, or maybe your manager is a singleton, etc. Note that this is a simplistic implementation of IDisposable, certain usages of this may cause PopContext
to be invoked more times than PushContext
, so you may want to improve the implementation to be safe.
class ExceptionManagerContext : IExceptionManagerContext, IDisposable
{
ExceptionManager manager;
public ExceptionManagerContext(ExceptionManager manager)
{
this.manager = manager;
manager.PushContext(this);
}
public bool EnableLogging { get; set; }
public void Dispose()
{
manager.PopContext();
}
}
Here is your code that wants to define a "context" in which exception handling should behave differently. The using
statement will ensure that the altered context is set, and then removed, safely. You can nest such using statements, run this code recursively, etc., and it should do the right thing.
using (new ExceptionManagerContext(exceptionManager) { EnableLogging = false })
{
DoSomethingThatMayRaiseExceptions();
}