Advanced c# programming 2: Decorator, proxy and AOP


Update: It appears a more complete solution for this is available for almost no money at NubiloSoft. I encourage everyone to download a trial of this and take a look!

Those of you that are familiar with AOP know that most AOP solutions come in the form of decorator patterns. More concrete: a method is changed to include a ‘before’ and an ‘after’ method.

Decorator patterns are especially useful when you want to change the functionality of a couple of methods or part of a program, while keeping it extendable. Things I use decorators for include:

  • Generic error filtering
  • Automatic failovers
  • Keeping statistics
  • Error logging
  • Security

In these cases, you basically want to extend your code, without having to implement new logging, security or whatever functionality. To be honest, I think these kinds of things are where AOP is the most useful; while AOP used to be a hype, I personally don’t see many other applications.

We start by making some assumptions about where we want to apply this. As I said, we are limited in what we can do, but what we can do is generate code on-the-fly and use interfaces to abstract from the decorators. For various reasons, it’s usually not possible or recommended to use inheritance instead of a proxy. So let’s say we use a proxy to call the original methods; what options do we have exactly?

  • We can decorate one or all methods
  • Call the decorator before or after the proxy method is called
  • Call the decorator when an uncaught exception occurs or when the proxy is successful

In other words, we want to have this:

using System; 
namespace DecoratorLibrary
{
    public enum DecoratorUsage<span style="color:black;"
    {
        Before,
        After,
        Success,
        OnException    }
 
    [AttributeUsage(AttributeTargets.Method, Inherited=true)]
    public class DecoratorAttribute : Attribute
												
    {
        public DecoratorAttribute(DecoratorUsage usage)
        {
            this.Usage = usage;
            this.MethodName = null;
        } 
 
        public DecoratorAttribute(DecoratorUsage usage, string methodName )
        {
            this.Usage = usage;
            this.MethodName = methodName;
        } 
 
        public DecoratorUsage Usage { get; private set; }
        public string MethodName { get; private set; }
    }
}

Now that that’s settled, let’s first focus on a small example to see where we’re heading…

    public interface IExample
										
    {
        void Foo();
        int Bar(string aap, string haar, int id);
    } 
 
    public class Example : IExample
												
    {
        public void Foo()
        {
            Console.WriteLine("Foo!");
        } 
 
        public int Bar(string aap, string haar, int id)
        {
            Console.WriteLine("Bar!");
            return (aap + haar + id).GetHashCode();
        }
    } 
 
    public class ExampleDecorator
										
    {
        [DecoratorAttribute(DecoratorUsage.Before)]
        public void CallBefore1(string methodName, object[] pars, ref object context)
        {
            Console.WriteLine("Before calling {0}", methodName);
        } 
 
        [DecoratorAttribute(DecoratorUsage.After)]
        public void CallAfter1(string methodName, object[] pars, ref object context)
        {
            Console.WriteLine("After calling {0}", methodName);
        } 
 
        [DecoratorAttribute(DecoratorUsage.Success)]
        public bool CallSuccess1(string methodName, object[] pars, object result, ref object context)
        {
            Console.WriteLine("Success calling {0}", methodName);
            return false;
        } 
 
        [DecoratorAttribute(DecoratorUsage.OnException)]
        public bool CallException1(string methodName, object[] pars, Exception exception, ref object context)
        {
            Console.WriteLine("Exception calling {0}", methodName);
            return false;
        } 
 
        [DecoratorAttribute(DecoratorUsage.Before, "Foo")]
        public void CallBefore2(string methodName, object[] pars, ref object context)
        {
            Console.WriteLine("Before calling {0}", methodName);
        } 
 
        [DecoratorAttribute(DecoratorUsage.After, "Foo")]
        public void CallAfter2(string methodName, object[] pars, ref object context)
        {
            Console.WriteLine("After calling {0}", methodName);
        } 
 
        [DecoratorAttribute(DecoratorUsage.Success, "Foo")]
        public bool CallSuccess2(string methodName, object[] pars, object result, ref object context)
        {
            Console.WriteLine("Success calling {0}", methodName);
            return false;
        } 
 
        [DecoratorAttribute(DecoratorUsage.OnException, "Foo")]
        public bool CallException2(string methodName, object[] pars, Exception exception, ref object context)
        {
            Console.WriteLine("Exception calling {0}", methodName);
            return false;
        }
    }

It’s important to notice that I added some Boolean return values where you wouldn’t expect them. These are for adding ‘retry’ support, which is useful for things like exception filtering and failovers. Also notice the context object that I pass along. This object is for anything you would like to have during scope of the method invocation.

We also want to be able to change the parameters and return types in our decorators. In other words, you are allowed to change ‘pars’ during method invocation. This is especially useful in the ‘Before’ phase.

As for the proxy, we want our code to generate something like this:

 
    public class ExampleProxy : IExample
												
    {
        public ExampleProxy(IExample target, ExampleDecorator decorator)
        {
            this.target = target;
            this.decorator = decorator;
        } 
 
        private IExample target;
        private ExampleDecorator decorator; 
 
[…]
 
        public int Bar(string aap, string haar, int id)
        {
            object context = null;
            object[] par = new object[] { aap, haar, id }; 
 
        retry:
            decorator.CallBefore1("Bar", par, ref context);
            try
						
            {
                int obj = target.Bar((string)par[0], (string)par[1], (int)par[2]);
                if (decorator.CallSuccess1("Bar", par, obj, ref context))
                {
                    goto retry;
                }
                return obj;
            }
            catch (Exception ex)
            {
                if (decorator.CallException1("Bar", par, ex, ref context))
                {
                    goto retry;
                }
                throw;
            }
            finally
						
            {
                decorator.CallAfter1("Bar", par, ref context);
            }
        }
    }

Easy enough, right? So let’s grab System.Reflection.Emit and start working. First we start with making a class that generates code. I use ILGenerator and System.Reflection.Emit to create myself a new assembly with a single module that I keep in memory. The first thing to do in this assembly is to create a Type and then add two fields to the type. Next, we want to initialize the fields with a constructor.

Then the tricky part begins: creating all the methods. This is done by enumerating all the methods in the decorator and creating a mapping of what needs to be implemented. Then the implementation itself begins: first the methods of the interface are enumerated, then the methods are created as implementation of the interface. When creating this kind of low-level code, especially the tools Reflector from Red Gate and PEVerify from Microsoft are a must-have; if you don’t have them, get them; they’re well worth the money.

Still with me? Time to create the code for the decorator generator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit; 
 
namespace DecoratorLibrary
{
    public class DecoratorProxyGenerator
										
    {
        public Type CreateDecorator(Type serviceInterface, Type decoratorType)
        {
            string fqn = "DECO" + Guid.NewGuid().ToString().Replace("-", ""); 
 
            // Create assembly
						
            AssemblyName assemblyName = new AssemblyName(fqn); 
 
            AssemblyBuilder assemblyBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.); 
 
            // Create module
						
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(fqn, false); 
 
            // Create type
						
            TypeBuilder typeBuilder = moduleBuilder.DefineType(
                fqn + ".Decorator",
                TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(serviceInterface); 
 
            // Construct the proxy object
						
            FieldBuilder proxyField =
                typeBuilder.DefineField("proxy", serviceInterface, FieldAttributes.Private);
            FieldBuilder decoratorField =
                typeBuilder.DefineField("decorator", decoratorType, FieldAttributes.Private); 
 
            // Create type constructor
						
            AddConstructor(typeBuilder, serviceInterface, proxyField, decoratorField); 
 
            // Create a mapping:
						
            // Methodname -> [Usage -> [Methods + additional info]]
						
            Dictionary<string, Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>>> dict =
                new Dictionary<string, Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>>>();
            foreach (MethodInfo mi in decoratorType.GetMethods())
            {
                object[] deco = mi.GetCustomAttributes(typeof(DecoratorAttribute), true);
                foreach (DecoratorAttribute attr in deco)
                {
                    if (attr.MethodName == null)
                    {
                        foreach (MethodInfo info in serviceInterface.GetMethods())
                        {
                            Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>> usages;
                            if (!dict.TryGetValue(info.Name, out usages))
                            {
                                usages = new Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>>();
                                dict.Add(info.Name, usages);
                            }
                            List<Tuple<MethodInfo, DecoratorAttribute>> ll;
                            if (!usages.TryGetValue(attr.Usage, out ll))
                            {
                                ll = new List<Tuple<MethodInfo, DecoratorAttribute>>();
                                usages.Add(attr.Usage, ll);
                            }
                            ll.Add(new Tuple<MethodInfo, DecoratorAttribute>(mi, attr));
                        }
                    }
                    else
						
                    {
                        Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>> usages;
                        if (!dict.TryGetValue(attr.MethodName ?? ".any", out usages))
                        {
                            usages = new Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>>();
                            dict.Add(attr.MethodName ?? ".any", usages);
                        }
                        List<Tuple<MethodInfo, DecoratorAttribute>> ll;
                        if (!usages.TryGetValue(attr.Usage, out ll))
                        {
                            ll = new List<Tuple<MethodInfo, DecoratorAttribute>>();
                            usages.Add(attr.Usage, ll);
                        }
                        ll.Add(new Tuple<MethodInfo, DecoratorAttribute>(mi, attr));
                    }
                }
            } 
 
            // Generate the code
						
            foreach (MethodInfo info in serviceInterface.GetMethods())
            {
                Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>> usages;
                if (!dict.TryGetValue(info.Name, out usages))
                {
                    usages = new Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>>();
                } 
 
                // Add the method contents
						
                AddMember(typeBuilder, info, serviceInterface, decoratorField, proxyField, usages);
            } 
 
            return typeBuilder.CreateType();
        } 
 
        private void AddConstructor(TypeBuilder typeBuilder, Type serviceInterface, FieldBuilder proxyField, FieldBuilder decoratorField)
        {
            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.HasThis,
                new Type[] { serviceInterface, decoratorField.FieldType }); 
 
            // Generate the constructor IL. 
						
            ILGenerator gen = constructorBuilder.GetILGenerator(); 
 
            // The constructor calls the base constructor with the string[] parameter.
						
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Call,
                     typeof(object).GetConstructor(Type.EmptyTypes)); 
 
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, proxyField); 
 
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_2);
            gen.Emit(OpCodes.Stfld, decoratorField); 
 
            gen.Emit(OpCodes.Ret);
        } 
 
        private void AddMember(TypeBuilder typeBuilder, MethodInfo info, Type serviceInterface,
                               FieldBuilder decoratorField, FieldBuilder proxyField,
                               Dictionary<DecoratorUsage, List<Tuple<MethodInfo, DecoratorAttribute>>> usages)
        {
            // Generate the method
						
            Type[] parameterTypes = info.GetParameters().Select((a) => (a.ParameterType)).ToArray();
            MethodBuilder mb = typeBuilder.DefineMethod(
                info.Name,
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot |
                MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.HasThis,
                info.ReturnType,
                parameterTypes); 
 
            bool isnotvoid = info.ReturnType != typeof(void); 
 
            // Generate the method variables
						
            ILGenerator gen = mb.GetILGenerator();
            var context = gen.DeclareLocal(typeof(object));
            var pars = gen.DeclareLocal(typeof(object[]));
            var obj = (isnotvoid) ? gen.DeclareLocal(info.ReturnType) : gen.DeclareLocal(typeof(object));
            var exc = gen.DeclareLocal(typeof(Exception)); 
 
            // Generate the code
						 
 
            //    object context = null;
						
            //    int CS$1$0000;
						
            //    object[] par = new object[] { aap, haar, id };
						 
 
            gen.Emit(OpCodes.Ldnull);
            gen.Emit(OpCodes.Stloc, context);
            gen.Emit(OpCodes.Ldnull);
            gen.Emit(OpCodes.Stloc, obj); 
 
            gen.Emit(OpCodes.Ldc_I4, parameterTypes.Length);
            gen.Emit(OpCodes.Newarr, typeof(object));
            gen.Emit(OpCodes.Stloc, pars);
            for (int i = 0; i < parameterTypes.Length; ++i)
            {
                gen.Emit(OpCodes.Ldloc, pars);
                gen.Emit(OpCodes.Ldc_I4, i);
                gen.Emit(OpCodes.Ldarg, i + 1);
                if (parameterTypes[i].IsValueType)
                {
                    gen.Emit(OpCodes.Box, parameterTypes[i]);
                }
                gen.Emit(OpCodes.Stelem_Ref);
            } 
 
            //again:
						
            Label again = gen.DefineLabel();
            Label endMethod = gen.DefineLabel();
            gen.MarkLabel(again); 
 
            //    this.decorator.CallBefore1("Bar", par, ref context);
						
            List<Tuple<MethodInfo, DecoratorAttribute>> usage;
            if (usages.TryGetValue(DecoratorUsage.Before, out usage))
            {
                foreach (var method in usage)
                {
                    gen.Emit(OpCodes.Ldarg_0);
                    gen.Emit(OpCodes.Ldfld, decoratorField);
                    gen.Emit(OpCodes.Ldstr, info.Name);
                    gen.Emit(OpCodes.Ldloc, pars);
                    gen.Emit(OpCodes.Ldloca, context);
                    gen.Emit(OpCodes.Callvirt, method.Item1);
                }
            }
            gen.BeginExceptionBlock();
            //    try
						
            //    {
						
            //        int obj = this.target.Bar((string) par[0], (string) par[1], (int) par[2]);
						
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, proxyField);
            for (int i = 0; i < parameterTypes.Length; ++i)
            {
                gen.Emit(OpCodes.Ldloc, pars);
                gen.Emit(OpCodes.Ldc_I4, i);
                gen.Emit(OpCodes.Ldelem_Ref);
                if (parameterTypes[i].IsValueType)
                {
                    gen.Emit(OpCodes.Unbox_Any, parameterTypes[i]);
                }
                else
						
                {
                    gen.Emit(OpCodes.Castclass, parameterTypes[i]);
                }
            }
            gen.Emit(OpCodes.Callvirt, info);
            if (isnotvoid)
            {
                gen.Emit(OpCodes.Stloc, obj);
            }
            //        if (this.decorator.CallSuccess1("Bar", par, obj, ref context))
						
            //        {
						
            //            goto Label_0021;
						
            //        }
						
            if (usages.TryGetValue(DecoratorUsage.Success, out usage))
            {
                foreach (var method in usage)
                {
                    gen.Emit(OpCodes.Ldarg_0);
                    gen.Emit(OpCodes.Ldfld, decoratorField);
                    gen.Emit(OpCodes.Ldstr, info.Name);
                    gen.Emit(OpCodes.Ldloc, pars);
                    gen.Emit(OpCodes.Ldloc, obj);
                    if (isnotvoid && info.ReturnType.IsValueType)
                    {
                        gen.Emit(OpCodes.Box, info.ReturnType);
                    }
                    gen.Emit(OpCodes.Ldloca, context);
                    gen.Emit(OpCodes.Callvirt, method.Item1); 
 
                    Label next = gen.DefineLabel();
                    gen.Emit(OpCodes.Brfalse, next);
                    gen.Emit(OpCodes.Leave, again);
                    gen.MarkLabel(next);
                }
            }
            gen.Emit(OpCodes.Leave, endMethod);
            //    }
						 
 
            gen.BeginCatchBlock(typeof(Exception));
            //    catch (Exception ex)
						
            //    {
						
            gen.Emit(OpCodes.Stloc, exc);
            if (usages.TryGetValue(DecoratorUsage.OnException, out usage))
            {
                //        if (this.decorator.CallException1("Bar", par, ex, ref context))
						
                //        {
						
                //            goto Label_0021;
						
                //        }
						
                foreach (var method in usage)
                {
                    gen.Emit(OpCodes.Ldarg_0);
                    gen.Emit(OpCodes.Ldfld, decoratorField);
                    gen.Emit(OpCodes.Ldstr, info.Name);
                    gen.Emit(OpCodes.Ldloc, pars);
                    gen.Emit(OpCodes.Ldloc, exc);
                    gen.Emit(OpCodes.Ldloca, context);
                    gen.Emit(OpCodes.Callvirt, method.Item1); 
 
                    Label next = gen.DefineLabel();
                    gen.Emit(OpCodes.Brfalse, next);
                    gen.Emit(OpCodes.Leave, again);
                    gen.MarkLabel(next);
                }
            }
            //        throw;
						
            gen.Emit(OpCodes.Rethrow);
            //    }
						 
 
            gen.BeginFinallyBlock();
            if (usages.TryGetValue(DecoratorUsage.After, out usage))
            {
                //    finally
						
                //    {
						
                //        this.decorator.CallAfter1("Bar", par, ref context);
						
                //    }
						
                foreach (var method in usage)
                {
                    gen.Emit(OpCodes.Ldarg_0);
                    gen.Emit(OpCodes.Ldfld, decoratorField);
                    gen.Emit(OpCodes.Ldstr, info.Name);
                    gen.Emit(OpCodes.Ldloc, pars);
                    gen.Emit(OpCodes.Ldloca, context);
                    gen.Emit(OpCodes.Callvirt, method.Item1);
                }
            }
            gen.EndExceptionBlock(); 
 
            gen.MarkLabel(endMethod);
            if (isnotvoid)
            {
                gen.Emit(OpCodes.Ldloc, obj);
            }
            gen.Emit(OpCodes.Ret);
        }
    }
}

In case you’re wondering… yes, this is even allowed in Silverlight!

Now all that remains is mapping the decorators on types. This isn’t too hard… simply cache the proxy’s that are generated and return a new proxy after generation.

using System;
using System.Collections.Generic; 
 
namespace DecoratorLibrary
{
    public class Decorator
										
    {
        private Decorator() { } 
 
        private class DecoratorPair
										
        {
            public Type DecoratorType;
            public Type InterfaceType; 
 
            public override bool Equals(object obj)
            {
                DecoratorPair pair = (DecoratorPair)obj;
                return pair.DecoratorType.Equals(DecoratorType) && 
                       pair.InterfaceType.Equals(InterfaceType);
            } 
 
            public override int GetHashCode()
            {
                return DecoratorType.GetHashCode() * 7577 + InterfaceType.GetHashCode();
            }
        } 
 
        private static Dictionary<DecoratorPair, Type> decorators = 
            new Dictionary<DecoratorPair, Type>();
        private static object decoratorObject = new object(); 
 
        public static T CreateDecorator<T, U>(T target, U decorator)
        {
            DecoratorPair pair = new DecoratorPair()
            {
                DecoratorType = typeof(U),
                InterfaceType = typeof(T)
            };
            Type t;
            lock (decoratorObject)
            {
                if (!decorators.TryGetValue(pair, out t))
                {
                    var generator = new DecoratorProxyGenerator();
                    t = generator.CreateDecorator(typeof(T), typeof(U));
                    decorators.Add(pair, t);
                }
            } 
 
            return (T)Activator.CreateInstance(t, target, decorator);
        }
    }
}

And we’re done. So time to test? Give it a test run:

            Example example = new Example();
            var decorator = Decorator.CreateDecorator<IExample, ExampleDecorator>(example, new ExampleDecorator());
            Console.WriteLine("Result = {0}", decorator.Bar("foo", "mek", 2));

… and let the fun begin.

,

  1. Leave a comment

Leave a comment