Friday, December 27, 2013

Understanding Unity Lifetime Managers

I absolutely love inversion of control, it is a best practice that I encourage developers to use in every project that they work on. However, like any great tool, dependency injection can cause serious problems if you do not fully understand it; lifetime management in particular.

As a developer you need to be cognisant of how many instances the container is constructing of your classes. You need to know when singleton is going to consume a transient or non thread safe resource. If you are not careful you can leak memory, share unsafe resources, or just make your garbage collector thrash.

Here is a series of examples regarding how to use lifetime managers in my favorite dependency injection container, Microsoft Unity.

  1. ResolveType
  2. Default Lifetime Manager
  3. TransientLifetimeManager
  4. RegisterInstance
  5. ContainerControlledLifetimeManager
  6. ExternallyControlledLifetimeManager
  7. ContainerControlledLifetimeManager with Multiple Keys
  8. RegisterType for Multiple Interfaces
  9. ContainerControlledLifetimeManager with Multiple Interfaces
  10. TransientLifetimeManager with Multiple Interfaces
  11. RegisterInstance Multiple Times
  12. RegisterInstance Multiple Times with External Lifetime Manager

Demo Classes

Here are the interfaces and demo class that we will be playing with in our tests. Note that the Demo class implements both interfaces, but only the IDemo interface is IDisposable.

public interface IAnotherDemo { }
        
public interface IDemo : IDisposable { }
 
public class Demo : IDemo, IAnotherDemo
{
    public static int ConstructorCounter {get; private set;}
    public static int DisposeCounter {get; private set;}
 
    public static void ResetCounters()
    {
        ConstructorCounter = 0;
        DisposeCounter = 0;
    }
 
    public Demo()
    {
        ConstructorCounter++;
    }
 
    public void Dispose()
    {
        DisposeCounter++;
    }
}

ResolveType

This first demo shows what happens when you resolve a concrete class without first registering that class with the container. It will be instantiated for each resolve (as though registered with a TransientLifetimeManager), all dependencies in its constructor will be resolved by the container, and it will not be disposed.

[Fact]
public void ResolveType()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demoA = container.Resolve<Demo>();
        var demoB = container.Resolve<Demo>();
 
        Assert.NotSame(demoA, demoB);
        Assert.Equal(2, Demo.ConstructorCounter);
    }
 
    Assert.Equal(0, Demo.DisposeCounter);
}

DefaultLifetimeManager

When an interface is registered with a container it defaults to using a TransientLifetimeManager.

[Fact]
public void DefaultLifetimeManager()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        container.RegisterType<IDemo, Demo>();
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
 
        Assert.NotSame(demoA, demoB);
        Assert.Equal(2, Demo.ConstructorCounter);
    }
 
    Assert.Equal(0, Demo.DisposeCounter);
}

TransientLifetimeManager

This test will yield the same results as above. The Demo class will be instantiated for each resolve, all dependencies in its constructor will be resolved by the container, and it will not be disposed of by the container.

[Fact]
public void TransientLifetimeManager()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var manager = new TransientLifetimeManager();
        container.RegisterType<IDemo, Demo>(manager);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
 
        Assert.NotSame(demoA, demoB);
        Assert.Equal(2, Demo.ConstructorCounter);
    }
 
    Assert.Equal(0, Demo.DisposeCounter);
}

RegisterInstance

Rather than registering a type to type conversion, you can just register a particular instance of an object with the container. This effectively makes that container treat that object as a singleton, and by default it will be disposed of with the container.

[Fact]
public void RegisterInstance()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demo = new Demo();
        container.RegisterInstance<IDemo>(demo);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
 
        Assert.Same(demoA, demoB);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(1, Demo.DisposeCounter);
}

ContainerControlledLifetimeManager

This is the most straight forward way to make a single class that is both instantiated and disposed by the container.

[Fact]
public void ContainerControlledLifetimeManager()
{
    Demo.ResetCounters();
            
    using (var container = new UnityContainer())
    {
        var manager = new ContainerControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>(manager);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
 
        Assert.Same(demoA, demoB);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(1, Demo.DisposeCounter);
}

ExternallyControlledLifetimeManager

The Externally Controlled LifetimeManager tells the container to let an external source manage the object, but it will instantiate the object for the first resolve if necessary. This effectively means that you are creating a singleton that the container will not dispose.

[Fact]
public void ExternallyControlledLifetimeManager()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var manager = new ExternallyControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>(manager);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
 
        Assert.Same(demoA, demoB);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(0, Demo.DisposeCounter);
}

ContainerControlledLifetimeManager with Multiple Keys

You may register the same interface multiple times with the container so long as you provide a key to discriminate between them. When you do this each registration has its own unique lifetime manager.

[Fact]
public void ContainerControlledLifetimeManagerWithKey()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var manager1 = new ContainerControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>("1", manager1);
 
        var manager2 = new ContainerControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>("2", manager2);
 
        Assert.Throws<ResolutionFailedException>(() => container.Resolve<IDemo>());
 
        var demo1A = container.Resolve<IDemo>("1");
        var demo1B = container.Resolve<IDemo>("1");
        var demo2A = container.Resolve<IDemo>("2");
        var demo2B = container.Resolve<IDemo>("2");
                
        Assert.Same(demo1A, demo1B);
        Assert.Same(demo2A, demo2B);
        Assert.NotSame(demo1A, demo2A);
        Assert.Equal(2, Demo.ConstructorCounter);
    }
 
    Assert.Equal(2, Demo.DisposeCounter);
}

RegisterType for Multiple Interfaces

This just serves as a reminder that container registration only respects the mapping you specify. Here we have registered Demo as IDemo, and thus we can not resolve IAnotherDemo even tough the Demo class implements that interface too.

[Fact]
public void RegisterTypeForMultipleInterfaces()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demoManager = new ContainerControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>(demoManager);
 
        var demo = container.Resolve<IDemo>();
 
        Assert.Throws<ResolutionFailedException>(() => container.Resolve<IAnotherDemo>());
 
        Assert.NotNull(demo);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(1, Demo.DisposeCounter);
}

ContainerControlledLifetimeManager with Multiple Interfaces

This is a very odd behavior, and personally I consider it to be a bug! When you map the same class to multiple interfaces, unity only respects the last LifetimeManager registered for that class.

[Fact]
public void ContainerControlledLifetimeManagerWithMultipleInterfaces()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demoManager = new TransientLifetimeManager();
        container.RegisterType<IDemo, Demo>(demoManager);
 
        var anotherManager = new ContainerControlledLifetimeManager();
        container.RegisterType<IAnotherDemo, Demo>(anotherManager);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
        var anotherDemoA = container.Resolve<IAnotherDemo>();
        var anotherDemoB = container.Resolve<IAnotherDemo>();
 
        Assert.Same(demoA, demoB);
        Assert.Same(anotherDemoA, anotherDemoB);
        Assert.Same(demoA, anotherDemoA);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(1, Demo.DisposeCounter);
}

TransientLifetimeManager with Multiple Interfaces

This is the same bug demonstrated in the previous test, only the container registration is reversed. Don't worry, further down I demonstrate better ways to register the same object with multiple interfaces.

[Fact]
public void TransientLifetimeManagerWithMultipleInterfaces()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demoManager = new ContainerControlledLifetimeManager();
        container.RegisterType<IDemo, Demo>(demoManager);
 
        var anotherManager = new TransientLifetimeManager();
        container.RegisterType<IAnotherDemo, Demo>(anotherManager);
 
        var demoA = container.Resolve<IDemo>();
        var demoB = container.Resolve<IDemo>();
        var anotherDemoA = container.Resolve<IAnotherDemo>();
        var anotherDemoB = container.Resolve<IAnotherDemo>();
 
        Assert.NotSame(demoA, demoB);
        Assert.NotSame(anotherDemoA, anotherDemoB);
        Assert.NotSame(demoA, anotherDemoA);
        Assert.Equal(4, Demo.ConstructorCounter);
    }
 
    Assert.Equal(0, Demo.DisposeCounter);
}

RegisterInstance Multiple Times

You can use register instance multiple times to resolve the same instance of an object for different interfaces. However, this will cause your object to be disposed of multiple times.

[Fact]
public void RegisterInstanceMultipleTimes()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demo = new Demo();
 
        container.RegisterInstance<IDemo>(demo);
        container.RegisterInstance<IAnotherDemo>(demo);
 
        var demoA = container.Resolve<IDemo>();
        var anotherDemoA = container.Resolve<IAnotherDemo>();
                
        Assert.Same(demoA, anotherDemoA);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(2, Demo.DisposeCounter);
}

RegisterInstance Multiple Times with External Lifetime Manager

For our final example we register the instance multiple times, but the first time we use a ContainerControlledLifetimeManager and after that we use ExternallyControlledLifetimeManager for the other registrations. This means that the dispose will only be called once against our object.

[Fact]
public void RegisterInstanceMultipleTimesWithExternal()
{
    Demo.ResetCounters();
 
    using (var container = new UnityContainer())
    {
        var demo = new Demo();
 
        var demoManager = new ContainerControlledLifetimeManager();
        container.RegisterInstance<IDemo>(demo, demoManager);
                
        var anotherManager = new ExternallyControlledLifetimeManager();
        container.RegisterInstance<IAnotherDemo>(demo, anotherManager);
 
        var demoA = container.Resolve<IDemo>();
        var anotherDemoA = container.Resolve<IAnotherDemo>();
 
        Assert.Same(demoA, anotherDemoA);
        Assert.Equal(1, Demo.ConstructorCounter);
    }
 
    Assert.Equal(1, Demo.DisposeCounter);
}
Shout it

Enjoy,
Tom

2 comments:

Real Time Web Analytics