Ron, >> Isn't the automatic clean-up more a feature of the >> using statement rather than the ThreadContext?
Sorry if I wasn't clear. I consider them intertwined because I wouldn't use
explicit (push/pop) ThreadContext without automatic clear-up. Too error prone.
I have tried to work out a scheme for anonymous delegates to provide context
safe exceptions but sadly there is too much dang strong typing so either you
have to declare a delegate type for anything you wish to execute contextually
or do what I attach below which is ugly in so many different ways! Basically it
requires that each level of context is a separate method, which is perfectly
acceptable (and often the case anyway), and then call it as a delegate but
sadly passing arguments requires filthy casting and its broken for ref, out,
value-types and return values.
I post it to see if it sparks the creativity of anyone else to find a more
effective scheme that is simpler and type-safe.
On a related note - the NDC Method 'CloneStack' doesn't work as I expected - it
doesn't return a copy but a reference that is manipulated without consent plus
the contained items are classes internal to log4net that don't have a sensible
ToString so you can't do diddly with them. This is why I had to pop and re-push
the ndc to copy it in NdcException. Doesn't FxCop warn about this?
anyway... on to the ugliness, believe it or not, it is functionally the same
example Ron gave before:
using System;
using System.Collections.Generic;
using System.Text;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Layout;
namespace ConsoleApplication
{
public delegate void ExecuteInContext(params object[] p);
// execute code passed as an anonymous delegate with context safe exceptions
public abstract class Context
{
public static void Execute(string context, ExecuteInContext call,
params object[] p)
{
using (NDC.Push(context))
{
try
{
call(p);
}
catch (Exception e)
{
if (!(e is NdcException)) throw new NdcException(e);
else throw;
}
}
}
}
// Wrap a normal exception and append the ndc stack - requires more effort
than it should
public class NdcException : Exception
{
public NdcException(Exception e)
: base(e.Message, e)
{
Stack<string> stack = new Stack<string>();
StringBuilder sb = new StringBuilder("Exception : ");
while (NDC.Depth > 0)
{
stack.Push(NDC.Pop());
}
while (stack.Count > 0)
{
string s = stack.Pop();
sb.AppendFormat("{0} ", s);
NDC.Push(s);
}
Ndc = sb.ToString();
}
public string Ndc;
}
class Program
{
static ConsoleAppender consoleAppender;
static ILog log;
static void Main(string[] args)
{
consoleAppender = new ConsoleAppender();
consoleAppender.Layout = new PatternLayout("MESSAGE [%message] NDC
[%ndc]%newline");
BasicConfigurator.Configure(consoleAppender);
log = LogManager.GetLogger(typeof(Program));
int[] oddNumbers = new int[5] { 1, 3, 5, 7, 9 };
int[] evenNumbers = new int[5] { 2, 4, 6, 8, 10 };
try
{
foreach (int oddNumber in oddNumbers)
{
Context.Execute(
"oddNumber: " + oddNumber,
delegate(object[] p) {EachEven((int) p[0], (int[])
p[1]);},
oddNumber, evenNumbers);
}
}
// Provide a contextual exception message
catch (NdcException ex)
{
using (NDC.Push(ex.Ndc))
{
log.Fatal(ex.Message);
}
}
catch (Exception ex)
{
log.Fatal("Should never happen: " + ex.Message, ex);
}
}
public static void EachEven(int oddNumber, int[] evenNumbers)
{
foreach (int evenNumber in evenNumbers)
{
Context.Execute(
"evenNumber: " + evenNumber,
delegate(object[] p) {TestOdd((int) p[0], (int) p[1]);},
oddNumber, evenNumber);
}
}
public static void TestOdd(int oddNumber, int evenNumber)
{
if (oddNumber == 7)
{
throw new ApplicationException("Oh no!");
}
log.DebugFormat("Sum: {0}", oddNumber + evenNumber);
}
}
}
Thanks,
Duncan
<<winmail.dat>>
