I've been playing with the Unity container and noticed a strange behavior. I have a class, which implements more than one interface. I want this class to be used in different places across an application with different lifetimes. So I have mapped IFooDerived1 to Foo as Singleton, and IFooDerived2 to Foo as Transient. But any registration of Foo corrupts previous registrations of Foo.
Sample:
interface IFoo { }
interface IFooDerived1 { }
interface IFooDerived2 { }
class Foo : IFoo, IFooDerived1, IFooDerived2 { }
static void Main(string[] args)
{
var container = new UnityContainer();
container.RegisterType(typeof(IFoo), typeof(Foo), new ExternallyControlledLifetimeManager());
container.RegisterType(typeof(IFooDerived1), typeof(Foo), new ContainerControlledLifetimeManager());
container.RegisterType(typeof(IFooDerived2), typeof(Foo), new TransientLifetimeManager());
foreach(var r in container.Registrations)
{
Console.WriteLine("{0} -> {1} : {2}", r.RegisteredType.Name, r.MappedToType.Name, r.LifetimeManagerType.Name);
}
}
Output:
IFoo -> Foo : TransientLifetimeManager
IFooDerived1 -> Foo : TransientLifetimeManager
IFooDerived2 -> Foo : TransientLifetimeManager
Is this correct behavior? Can anybody give some logical explanation? I can easily use some other approach, I just want to understand why this happening. Thanks.
If you change the registration to the following:
container.RegisterType(typeof(IFooDerived1), typeof(Foo), new ContainerControlledLifetimeManager());
container.RegisterType(typeof(IFooDerived2), typeof(Foo), new TransientLifetimeManager());
container.RegisterType(typeof(IFoo), typeof(Foo), new ExternallyControlledLifetimeManager());
you'll get the following output:
IFoo -> Foo : ExternallyControlledLifetimeManager
IFooDerived1 -> Foo : ExternallyControlledLifetimeManager
IFooDerived2 -> Foo : ExternallyControlledLifetimeManager
WAT? No matter what abstraction you retrieve, you will always get the same instance. So just by changing the order of the registrations you get completely different behavior of the lifestyle.
btw, the ExternallyControlledLifetimeManager
is scary as fuck, because it uses a weak reference to the object and could recreate it after the application no longer keeps a reference to it. You should hardly ever use this lifestyle.
I'm trying to get my head around this, but it seems that in Unity, when you make a registration you are actually registering the implementation with a certain lifestyle and the supplied abstraction just maps to that registration. So if we define an alternative API for Unity, the registration would becomes something like this:
MakeEntry(typeof(Foo), new ExternallyControlledLifetimeManager());
MapToEntry(from: typeof(IFoo), to: typeof(Foo));
So with this 'alternative' API, it becomes more clear that the following is happening:
MakeEntry(typeof(Foo), new ExternallyControlledLifetimeManager());
MapToEntry(from: typeof(IFoo), to: typeof(Foo));
MakeEntry(typeof(Foo), new ExternallyControlledLifetimeManager());
MapToEntry(from: typeof(IFooDerived1), to: typeof(Foo));
MakeEntry(typeof(Foo), new TransientLifetimeManager());
MapToEntry(from: typeof(IFooDerived2), to: typeof(Foo));
Which means there are three entries for the same Foo
and apparently Unity simply accepts this silently and the last call wins. Don't you hate this implicit behavior?
Time to switch to a different library perhaps?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With