Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity RegisterType with LifetimeManager strange behavior

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.

like image 937
boades Avatar asked Sep 24 '14 20:09

boades


1 Answers

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?

like image 148
Steven Avatar answered Nov 10 '22 08:11

Steven