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
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);
}
}
}
Enjoy,
Tom