Sunday, July 29, 2012

Lazy Unity Injection

Adapting a legacy project to use proper Dependency Injection can be difficult.

Often times your services can not be singletons, and then those dependencies cause chains of other services to require transient lifetimes. Sometimes these service dependency trees can grow deep, and their initialization can become quite expensive. If you are working with a website, those big trees can add up and cost a lot of time and memory with every request.

One way to trim a deep or expensive dependency tree is to inject Lazy types of your services.

Lazy injection will prevent an optional resource from being instantiated until it is actually needed. While this is not a scenario that you want to architect your application into voluntarily, it can be a crucial performance optimization. Have you ever taken a memory dump of your website and found thousands of services floating in memory? I have, and let me assure you, it's not pretty!

LazyExtension for Unity

public class LazyExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        Context.Policies.Set<IBuildPlanPolicy>(
            new LazyBuildPlanPolicy(), 
            typeof(Lazy<>));
    }
 
    public class LazyBuildPlanPolicy : IBuildPlanPolicy
    {
        public void BuildUp(IBuilderContext context)
        {
            if (context.Existing != null)
                return;
 
            var container = context.NewBuildUp<IUnityContainer>();
            var typeToBuild = context.BuildKey.Type.GetGenericArguments()[0];
            var nameToBuild = context.BuildKey.Name;
            var lazyType = typeof(Lazy<>).MakeGenericType(typeToBuild);
 
            var func = GetType()
                .GetMethod("CreateResolver")
                .MakeGenericMethod(typeToBuild)
                .Invoke(this, new object[] { container, nameToBuild });
 
            context.Existing = Activator.CreateInstance(lazyType, func);
 
            DynamicMethodConstructorStrategy.SetPerBuildSingleton(context);
        }
 
        public Func<T> CreateResolver<T>(
            IUnityContainer currentContainer, 
            string nameToBuild)
        {
            return () => currentContainer.Resolve<T>(nameToBuild);
        }
    }
}

Unit Tests

public interface ITestClass
{
    int HighFive();
}
 
public class TestClass : ITestClass
{
    public static int InstanceCount = 0;
 
    public TestClass()
    {
        Interlocked.Increment(ref InstanceCount);
    }
 
    public int HighFive()
    {
        return 5;
    }
}
 
[TestFixture]
public class TestFixture
{
    [Test]
    public void Test()
    {
        using (var container = new UnityContainer())
        {
            container.RegisterType<ITestClass, TestClass>();
            container.AddNewExtension<LazyExtension>();
 
            var testClass1 = container.Resolve<Lazy<ITestClass>>();
 
            Assert.AreEqual(false, testClass1.IsValueCreated);
            Assert.AreEqual(0, TestClass.InstanceCount);
 
            Assert.AreEqual(5, testClass1.Value.HighFive());
            Assert.AreEqual(true, testClass1.IsValueCreated);
            Assert.AreEqual(1, TestClass.InstanceCount);
 
            var testClass2 = container.Resolve<Lazy<ITestClass>>();
 
            Assert.AreEqual(false, testClass2.IsValueCreated);
            Assert.AreEqual(1, TestClass.InstanceCount);
 
            Assert.AreEqual(5, testClass2.Value.HighFive());
            Assert.AreEqual(true, testClass2.IsValueCreated);
            Assert.AreEqual(2, TestClass.InstanceCount);
        }
    }
}
kick it on DotNetKicks.com

Enjoy,
Tom

Real Time Web Analytics