I work on a project that uses Unity dependency injection framework. You get objects from container by calling Container.Resolve<SomeType>()
. By default each call to Resolve()
will return a new object. You can switch to singleton mode by calling Container.RegisterType<SomeType>(new ContainerControlledLifetimeManager())
. The singleton, if created, will be disposed when the container itself is disposed.
Lately I started playing with nested containers. My application is large and composable, and nested containers look like a nice way to organize scope/modularity. A particular part of the application can introduce objects in a nested container that the rest of the world does not know/care about.
I discovered that presence nested containers and singletons creates a number of permutations, and some permutations lead to weird and probably unwanted results. I wrote some tests, and here’s the summary of the most interesting discoveries.
1. If type A is configured as singleton in parent container, and as default in child container, Resolve<A>()
call on either container will return a parent-level singleton with lifetimed linked to the parent.
Container.RegisterType<A>(new ContainerControlledLifetimeManager()); var a1 = ChildContainer.Resolve<A>(); var a2 = Container.Resolve<A>(); Assert.AreSame(a1, a2);
2. If type A is configured as singleton on both containers, two separate singleton instances will be created, each bound to its container lifetime.
Container.RegisterType<A>(new ContainerControlledLifetimeManager()); ChildContainer.RegisterType<A>(new ContainerControlledLifetimeManager()); var a11 = ChildContainer.Resolve<A>(); var a12 = ChildContainer.Resolve<A>(); var a21 = Container.Resolve<A>(); var a22 = Container.Resolve<A>(); Assert.AreSame(a11, a12); Assert.AreSame(a21, a22); Assert.AreNotSame(a11, a21);
3. Here’s the most spectacular and weird one. Let’s say type B depends on A. Type A is configured as singleton in both containers. Type B is configured as singleton in parent container, but as default in child container.
Under these circumstances the configuration of the object depends on the container through which first invocation of Resolve<B>()
was made. All subsequent invocations through any container will return the originally created object.
If the first container is parent, everything works fine. But if the fisrt container is child, things get icky. The object B is installed as a parent-level singleton, as per rule #1. However, its dependency of type A is installed as a child-level singleton with child-bound lifetime as per rule #2. Even if the parent container already contains an object of type A, it will be ignored. When child container is disposed, the main object B will stay intact, but its dependency A will be gone.
And it all depends on the location of first instantiation, which may be not so easy to figure out, and it may change unexpectedly. Hello, bugs!
Container.RegisterType<A>(new ContainerControlledLifetimeManager()); Container.RegisterType<B>(new ContainerControlledLifetimeManager()); ChildContainer.RegisterType<A>(new ContainerControlledLifetimeManager()); var a1 = ChildContainer.Resolve<A>(); var a2 = Container.Resolve<A>(); var b1 = ChildContainer.Resolve<B>(); var b2 = Container.Resolve<B>(); Assert.AreSame(b2.A, a1); /*!*/