Saturday, March 28, 2009

Delegates in Unity - 3

Download: click here

Here I explain the guts of DelegateInterceptionExtension which is used to register delegate implementations and interceptors for those delegates.

First I needed to create a class inheriting from UnityContainerExtension. Then add the methods that the user of this extension would be using to do what the extension is supposed to do.

The main method is "RegisterDelegate" which takes the following parameters: the type of delegate to register, an implementation for it, an optional name for the implmentaion instance, and finally a list of call handlers:

public class DelegateInterceptionExtension : UnityContainerExtension
{
public DelegateInterceptionExtension RegisterDelegate(Type delegateType,
Delegate methodImpl,
string name,
ICallHandler[] handlerInstances)
{
NamedTypeBuildKey key =
new NamedTypeBuildKey(delegateType, name);
DelegateInterceptionPolicy policy =
new DelegateInterceptionPolicy(Context.Container, handlerInstances);
Context.Policies.Set(policy, key);
Context.Container.RegisterInstance(
delegateType,
methodImpl,
new ontainerControlledLifetimeManager());
return this;
}
protected override void Initialize()
{
Context.Strategies.AddNew(UnityBuildStage.Setup);
}
}


In addition to registering the given instance with Unity, this extension creates a DelegateInterceptionStrategy and a DelegateInterceptionPolicy object. The strategy object is needed for when the instance is being looked up the first time by Unity (or rather by the underlying ObjectBuilder). Since we register an instance of a delegate we set the strategy stage to Setup (or PostBuild).

The policy object is a helper object that is later used by the strategy. The policy carries the call handlers for the given registration.

The strategy class inherits from BuilderStrategy and implements the method PostBuildUp():

public class DelgateInterceptionStrategy : BuilderStrategy
{
public override void PostBuildUp(IBuilderContext context)
{
Type theType = BuildKey.GetType(context.OriginalBuildKey);
DelegateInterceptionPolicy policy =
context.Policies.Get<DelegateInterceptionPolicy>(context.OriginalBuildKey, false);
if (policy != null && policy.GetHandlerCount() > 0)
{
HandlerPipeline pipeline =
new HandlerPipeline(policy.GetHandlers());
DelegateInterceptor interceptor =
new DelegateInterceptor(pipeline, (Delegate)context.Existing);
context.Existing = interceptor.GetInterceptingMethod(theType);
}
}
}


When PostBuildUp is called, it checks to see if there are call handlers. If not, there is nothing to do and it lets the container return the initially registered instance.

If any call handlers were speficied for the resolved type (or type and instance name) then it creates an instance of DelegateInterceptor. This interceptor has a "dynamic" method that provides a new implementation for the requested delegate. In place of the originally registered implementation, the intercepting implementation is returned to caller. This dynamic implementation has the same signature as the delegate itself.

The DelegateInterceptor class, takes the original target and a list pipeline object (created from the call handlers). The pipeline object is taken from the Unity interception extension:

public class DelegateInterceptor
{
private HandlerPipeline pipeline;
private Delegate target;

public DelegateInterceptor(HandlerPipeline pipeline, Delegate target)
{
this.pipeline = pipeline;
this.target = target;
}
...
}


The interceptor class needs to ultimately invoke the target instance (the originally registered instance) once all call handlers in the pipeline are called. This process is listed below and is pretty much taken from the source code for Unity:

public object InterceptingMethodBody(object[] targetArgs)
{
IMethodReturn result = pipeline.Invoke(
new VirtualMethodInvocation(null, target.Method, targetArgs),
(IMethodInvocation inputs, GetNextHandlerDelegate getNext) =>
{
try
{
// this is where we call the original target
object returnValue =
target.Method.Invoke(target.Target, targetArgs);
if(target.Method.ReturnType == typeof(void))
{
return inputs.CreateMethodReturn(null);
}
return inputs.CreateMethodReturn(returnValue);
}
catch (Exception ex)
{
return inputs.CreateExceptionMethodReturn(ex);
}
});

if (result.Exception != null)
throw result.Exception;
return result.ReturnValue;
}


As you can see from its signature, this method takes an array of objects and passes them along to the call chain. These arguments are supplied by the user to the called delegate and they are entirely based on the registered delegate.

The above method cannot be passed to the user yet since it does not comply with the signature of the registered delegate (unless the delegate itself happens to take an object[] as the only parameter). So an impersonator, with the same signature as the delegate, is needed to take the call parameters, stack them in the form of an object[], and pass them to the interceptor.

For that we need the help of IL generation. DelegateInterceptor has a method to do that:

public Delegate SubstituteDelegate(Type typeToSubstitute)
{
MethodInfo interceptorInfo = this.GetType().GetMethod("InterceptingMethodBody");
MethodInfo methodInfo = typeToSubstitute.GetMethod("Invoke");
ParameterInfo[] paramInfos = methodInfo.GetParameters();
Type[] types = new Type[paramInfos.Length + 1];
// the very first argument to the new method will be the enclosed object
types[0] = this.GetType();
for (int i = 0; i < paramInfos.Length; i++)
{
types[i+1] = paramInfos[i].ParameterType;
}
DynamicMethod dm = new DynamicMethod(typeToSubstitute.ToString() + "Substitute",
methodInfo.ReturnType,
types,
this.GetType());
ILGenerator il = dm.GetILGenerator();
// emit: object[] arr = new object[paramInfos.Length];
il.DeclareLocal(typeof(Object[]));
il.Emit(OpCodes.Ldc_I4, paramInfos.Length);
il.Emit(OpCodes.Newarr, typeof(Object));
il.Emit(OpCodes.Stloc_0);
// emit: arr[i] = arg_i+1;
for (int i = 0; i < paramInfos.Length; i++)
{
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldarg, i+1);
if (paramInfos[i].ParameterType.IsValueType)
{
il.Emit(OpCodes.Box, paramInfos[i].ParameterType);
}
il.Emit(OpCodes.Stelem_Ref);
}
// emit: interceptorInfo.Invoke(obj, arr);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Call, interceptorInfo);
// emit: return // void
if (methodInfo.ReturnType == typeof(void))
{
il.Emit(OpCodes.Pop);
}
// emit: return (methodInfo.ReturnTpe)result
else
{
if (methodInfo.ReturnType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType);
}
}
il.Emit(OpCodes.Ret);
return dm.CreateDelegate(typeToSubstitute, this);
}


All this method does is create a delegate implementation. This helper implementation takes the passed arguments (i.e. method(arg0, arg1, arg2)) and passes them to "InterceptingMethodBody" method of the interceptor.

In the supplied code, I have provided a whole bunch of test cases to ensure this part of the code is behaving properly.

I'm sure there are a lot complexities that I still have not considered here but I'm hoping that this would provide you with enough base to go on if you find the delegate interception concept useful.

I hope you benfited form this post. Comments are appreciated.

1 comment:

  1. Hi Jeff,
    Thanks for your articles on delegates in Unity. They are clear and helpful.

    Do you have any example of how to use Unity configuration to declare a delegate and inject it as dependency into a constructor?

    Thanks,
    Paul

    ReplyDelete