04 Context Stacks

Everything is local in object orientated systems, that is, if the current object does not have the required context at hand, it is a pity to integrate it later on. The recurring pattern here is to add yet another parameter to a bunch of methods so that the new required context is available.

This might seem unusual, but sometimes, for heavily nested generators for example, I favor context stacks instead. This works only for environments where an object can only be accessed by one thread at a time (which should be the default choice). Using the [ThreadStatic] attribute on static fields in combination with static generic classes, a thread local and type dependent context stack can be implemented in a very cheap way:

[The implementation requires DisposeAction ]

public static class Context<TypeT>
{
  public static IDisposable push(TypeT value)
  { return Stack.push(value); }
 
  public static TypeT Current
  { get { return Stack.Current; } }
 
  static ContextStack Stack
  { get { return StackInternal ?? (StackInternal = new ContextStack()); }}
 
  [ThreadStatic]
  static ContextStack StackInternal;
 
  sealed class ContextStack
  {
    readonly Stack<TypeT> Stack = new Stack<TypeT>();
    readonly IDisposable PopAction;
 
    public ContextStack()
    {
      PopAction = new DisposeAction(pop);
    }
 
    public IDisposable push(TypeT value)
    {
      Stack.Push(value);
      return PopAction;
    }
 
    void pop()
    {
      Debug.Assert(Stack.Count != 0);
      Stack.Pop();
    }
 
    public TypeT Current
    {
      get 
      {
        Debug.Assert(Stack.Count != 0);
        return Stack.Peek();
      }
    }
  }
}
 
// handy shortcut
 
public static class Context
{
  public static IDisposable push<TypeT>(TypeT value)
  { return Context<TypeT>.push(value); }
}

Now instead of adding a parameter, you may simply be able to set up a context at any time.

using(Context.push(new MyContext()))
{
  callSomeDeeplyNestedMethods();
}

And at any time, in some of the nested methods, call

var myContext=Context<MyContext>.Current;

to retrieve the context.

The context can be nested, and the context stack of different types can be used without interfering.

Edited: The initialization of a [ThreadStatic] must be lazy, otherwise a second thread would only see a null pointer.