Skip to content

State-aware programming in C#, part III

Now that we have a syntax for defining state aware objects, we need the code that wires these things up for us automatically. This consists of the following:

  • StateMethod Attribute
  • StateMethodHandler Attribute
  • AStateObject base class to wire up our methods

The StateMethod Attribute

This attribute simply marks the method as a state aware method. As previously illustrated, there is a lot of manual footwork to get all the plumbing in place for a method marked as such. I'm still trying to figure out whether I can simplify that some more and let the baseclass do the work. The other option is a code generator that spits out the appropriate block into separate file as a partial class definition.

The method delegate argument that the constructor takes is the delegate that the method calls to get its work done.

namespace Droog.Stately
{
  [AttributeUsage(AttributeTargets.Method)]
  public class StateMethod : Attribute
  {
    string methodDelegateName;

    public StateMethod(string methodDelegateName)
    {
      this.methodDelegateName = methodDelegateName;
    }

    public string MethodDelegateName
    {
      get { return methodDelegateName; }
    }
  }
}

The StateMethodHandler Attribute

This attribute tags methods as handlers for state methods. As such they must have the same signature (although they can be public, private, protected, whatever) as the actual method, or more to the point, the same signature as the delegate that the state method calls. It's conceivable that the state method adds another argument, although i can't really see why.

There are two types of StateMethodHandlers: Default and state specific.

namespace Droog.Stately
{
  [AttributeUsage(AttributeTargets.Method)]
  public class StateMethodHandler : Attribute
  {
    string methodName;
    byte state;
    bool isDefault;

    public StateMethodHandler(string methodName, byte state)
    {
      this.methodName = methodName;
      this.state = state;
      this.isDefault = false;
    }
    public StateMethodHandler(string methodName)
    {
      this.methodName = methodName;
      this.isDefault = true;
    }

    public bool IsDefault
    {
      get { return isDefault; }
    }

    public string MethodName
    {
      get { return methodName; }
    }

    public byte State
    {
      get { return state; }
    }
  }
}

The AStateObject abstract base class

This is a very simplistic implementation. Note that it assumes that the derived class does everything right. I.e. if you forgot the default handler it would die unceremonously, if you had multiple handlers declared for the same things, it would just take the last one, etc.

It also handles only Methods. It should probably be extended to at least properties. But extending it to event handlers could be pretty powerful as well.

The mechanism tries to do as much as possible at first instantiation to both reduce the overhead and allow it to fail early instead of at call time, should the definition be wrong. If performance was a problem, the creation of the delegates could be moved to instantiation as well, and done at each instantiation. It would create an increased overhead for instantiation and a larger memory footprint, so it's a modification that needs to be dictated by use.

namespace Droog.Stately
{
  public class AStateObject
  {
    /// <summary>
    /// Internal collection for storing the mappings for state delegation. 
    /// </summary>
    private class StateDelegateMap
    {
      MethodInfo[] stateDelegateMethodInfo = new MethodInfo[10];
      MethodInfo invokingMethodInfo;
      FieldInfo delegateFieldInfo;
      MethodInfo delegateMethodInfo;

      public MethodInfo this[byte idx]
      {
        get { CheckBounds(idx); return stateDelegateMethodInfo[idx]; }
        set { CheckBounds(idx); stateDelegateMethodInfo[idx] = value; }
      }

      private void CheckBounds(byte idx)
      {
        if (idx >= stateDelegateMethodInfo.Length)
        {
          MethodInfo[] newStorage = new MethodInfo[idx + 1];
          stateDelegateMethodInfo.CopyTo(newStorage, 0);
          stateDelegateMethodInfo = newStorage;
        }
      }

      public MethodInfo InvokingMethodInfo
      {
        get { return invokingMethodInfo; }
        set { invokingMethodInfo = value; }
      }

      public FieldInfo DelegateFieldInfo
      {
        get { return delegateFieldInfo; }
        set { delegateFieldInfo = value; }
      }

      public MethodInfo DefaultDelegateMethodInfo
      {
        get { return delegateMethodInfo; }
        set { delegateMethodInfo = value; }
      }
    }

    /// <summary>
    /// Internal Dictionary for mapping methods to delegation maps.
    /// This is really just a shorthand for a typed dictionary
    /// </summary>
    private class StateMethodMap
      : Dictionary<string, StateDelegateMap>
    {
    }

    /// <summary>
    /// Static storage for our mappings. Base class stores them all
    /// and lazy-initialized them the first time an instance of
    /// a Type is created
    /// </summary>
    static Dictionary<Type, StateMethodMap> stateDelegationMap
      = new Dictionary<Type, StateMethodMap>();

    protected byte internalStateId;

    protected AStateObject()
    {
      InitStateHandlers();
    }

    /// <summary>
    /// The internal representation of states as bytes. It's up
    /// to the deriving class to decide whether an Accessor should
    /// be exposed and that datatype (recommeneded enum) is used
    /// to represent the class's states.
    /// </summary>
    protected byte InternalStateId
    {
      get { return internalStateId; }
      set
      {
        internalStateId = value;
        InitState();
      }
    }

    /// <summary>
    /// This creates the static mapping of state methods to possible
    /// delegates. It does not create delegates because they are tied
    /// to instances and therefore have to be created for each
    /// instance. This creation happens in <see cref="InitState"/>
    /// </summary>
    private void InitStateHandlers()
    {
      Type t = GetType();
      if (stateDelegationMap.ContainsKey(t))
      {
        // we already generated this map, so we can skip this step
        return;
      }

      MethodInfo[] methods = t.GetMethods(
        BindingFlags.Instance |
        BindingFlags.NonPublic |
        BindingFlags.Public);

      StateMethodMap methodMap = new StateMethodMap();
      stateDelegationMap.Add(t, methodMap);

      // find all state methods for this type
      foreach (MethodInfo m in methods)
      {
        foreach (StateMethod attr 
          in m.GetCustomAttributes(typeof(StateMethod), true))
        {
          FieldInfo delegateField = t.GetField(
            attr.MethodDelegateName,
            BindingFlags.Instance |
            BindingFlags.NonPublic);

          StateDelegateMap delegateMap = new StateDelegateMap();
          delegateMap.InvokingMethodInfo = m;
          delegateMap.DelegateFieldInfo = delegateField;
          methodMap.Add(m.Name, delegateMap);
        }
      }

      // find all state delegates for this type
      foreach (MethodInfo m in methods)
      {
        foreach (StateMethodHandler attr 
          in m.GetCustomAttributes(typeof(StateMethodHandler), true))
        {
          if (methodMap.ContainsKey(attr.MethodName))
          {
            StateDelegateMap delegateMap = methodMap[attr.MethodName];
            if (attr.IsDefault)
            {
              // default method handler
              delegateMap.DefaultDelegateMethodInfo = m;
            }
            else
            {
              // state specific method handler
              delegateMap[attr.State] = m;
            }
          }
        }
      }
    }

    /// <summary>
    /// This gets called every time we change the stateId and creates the
    /// appropriate delegates from the static mapping
    /// </summary>
    private void InitState()
    {
      StateMethodMap methodMap = stateDelegationMap[GetType()];

      foreach (StateDelegateMap delegateMap in methodMap.Values)
      {
        MethodInfo delegateMethodInfo = null;
        if (delegateMap[InternalStateId] != null)
        {
          // got a state specific method, let's map it
          delegateMethodInfo = delegateMap[InternalStateId];
        }
        else
        {
          // no state specific method, use the default
          delegateMethodInfo = delegateMap.DefaultDelegateMethodInfo;
        }
        Delegate stateDelegate = Delegate.CreateDelegate(
          delegateMap.DelegateFieldInfo.FieldType,
          this,
          delegateMethodInfo);

        delegateMap.DelegateFieldInfo.SetValue(this, stateDelegate);
      }
    }
  }
}

Now we have a fairly simple framework to create objects in our simulation that can alter their behavior depending on their state. As I start writing code with this framework, it'll probably get fleshed out a bit more and I'll find out whether the design is sustainable, since right now it's a clean-room design more than anything else.