TypedFactory reloaded
August 5, 2014 Leave a comment
Another piece of code that I wrote some time ago came back to my attention recently and I decided to give it the same treatment as the SmartConstructor and do some cleanup on the TypedFactory port I did for Unity.
One thing I really disliked at the time was that the Unity Interception Extension cannot generate a “proxy without target” out of the box. If you want to intercept calls to an object you actually need to have that object. A pipeline with no target isn’t possible with Unity.
As I had experimented with IL code generation before I took the long way round and built a NullObject generator. Not pretty. But it worked.
Another issue was the need to fumble an instance of the container into the interception pipeline by putting it in a dictionary, retrieving it down the pipeline, cast it back to a container and then start working with it. For the life of me I couldn’t find an architecturally clean way to solve that issue then. But the ugly one worked.
If you can’t see the forest for the trees you need to take a step back. A year later I took a look at a post that I had read back then and all of a sudden the pieces fell into place.
As an answer to a question in the Unity discussion forum Chris Tavares solved both of my problems. I just didn’t get it the first time.
The code snippet to generate a proxy without a target (or more precisely an interception pipeline without a target) is really simple.
Intercept.NewInstanceWithAdditionalInterfaces( Type type, ITypeInterceptor interceptor, IEnumerable<IInterceptionBehavior> interceptionBehaviors, IEnumerable<Type> additionalInterfaces, params object[] constructorParameters)
This method gives us all that we need. The type
parameter lets you specify the type of the target object. As we don’t actually need a target typeof(object)
is good enough. The VirtualMethodInterceptor
from Chris’ answer is what we will use as interceptor
. The factory interface we want to implement goes into the additionalInterfaces
. As our target is a simple object we don’t need to bother about constructorParameters
.
The actual factory logic is implemented as an IInterceptionBehavior
called FactoryBehavior
(I’m all for self-explanatory names by the way). But that behavior still needs an instance of the container which is just not available at the place this is all wired up.
Again, Chris’ post gave me a solution that I just didn’t recognize.
Anyway, to hook it up to the container, the quickest thing to do would be to use
InjectionFactory
to call theIntercept.NewInstance
API.
InjectionFactory
is derived from InjectionMember
(as is TypedFactory
). InjectionMembers
are Unity’s way to make very specific additions to the build pipeline of a type. An InjectionFactory
tells the container how to construct an object instead of letting the container figure out how to do that itself.
InjectionFactory
has two constructors that each take a delegate that lets you specify how to create your object of interest.
I’ll just show you the signature of the greedier one of the two.
public InjectionFactory(Func<IUnityContainer, Type, string, object> factoryFunc)
The first parameter is a container out of the depths of the Unity infrastructure. And its totally for free. Below you see the new implementation of the AddPolicies(Type, Type, string, IPolicyList)
method of the TypedFactory
.
public override void AddPolicies(Type ignore, Type factoryType, string name, IPolicyList policies) { if (factoryType.IsInterface) { InjectionFactory injectionFactory = new InjectionFactory( (container, t, n) => Intercept.NewInstanceWithAdditionalInterfaces( typeof(object), new VirtualMethodInterceptor(), new IInterceptionBehavior[] { new FactoryBehavior(container, this.selector) }, new[] { factoryType })); injectionFactory.AddPolicies(ignore, factoryType, name, policies); } else if (factoryType.IsAbstract) { InjectionFactory injectionFactory = new InjectionFactory( (container, t, n) => Intercept.NewInstance( factoryType, new VirtualMethodInterceptor(), new IInterceptionBehavior[] { new FactoryBehavior(container, this.selector) })); injectionFactory.AddPolicies(ignore, factoryType, name, policies); } else { throw new ArgumentException("'factoryType' must either be an interface or an abstract class.", "factoryType"); } }
It supports interfaces and abstract classes as factoryType
. Based on that distinction we either use the Intercept.NewInstanceWithAdditionalInterfaces
or Intercept.NewInstance
method to create our factory implementation. The container provided by the InjectionFactory
gets forwarded into our FactoryBehavior
and we are done. The rest of the implementation stays the same.
Now this is more like it. We reuse large parts of the Unity infrastructure instead of reinventing the wheel and quite a bit of code becomes obsolete. As usual you can find the code for the updated TypedFactory
on my CodePlex site (project TecX.Unity[Factories] and the tests that show how it works under TecX.Unity.Test[Factories]).