views:

244

answers:

2

I've got a CustomersModule.cs with the following Initialize() method:

public void Initialize()
{
    container.RegisterType<ICustomersRepository, CustomersRepository>(new ContainerControlledLifetimeManager());
    CustomersPresenter customersPresenter = this.container.Resolve<CustomersPresenter>();

}

The class I resolve from the container looks like this:

class CustomersPresenter
{
    private CustomersView view;
    private ICustomersRepository customersRespository;

    public CustomersPresenter(CustomersView view, 
     ICustomersRepository customersRepository, 
     TestWhatever testWhatever)
    {
        this.view = view;
        this.customersRespository = customersRepository;
    }
}

The TestWhatever class is just a dummy class I created:

public class TestWhatever
{
    public string Title { get; set; }

    public TestWhatever()
    {
        Title = "this is the title";
    }

}

Yet the container happily resolves CustomersPresenter even though I never registered it, and also the container somehow finds TestWhatever, instantiates it, and injects it into CustomersPresenter.

I was quite surprised to realize this since I couldn't find anywhere in the Prism documentation which explicitly stated that the container was so automatic.

So this is great, but it what else is the container doing that I don't know about i.e. what else can it do that I don't know about? For example, can I inject classes from other modules and if the modules happen to be loaded the container will inject them, and if not, it will inject a null?

+2  A: 

There is nothing magical going on. You are specifying concrete types, so naturally they are resolvable, because if we have the Type object, we can call a constructor on it.

class Fred { };

Fred f1 = new Fred();

Type t = typeof(Fred);

Fred f2 = (Fred)t.GetConstructor(Type.EmptyTypes).Invoke(null);

The last line above is effectively what happens, the type t having been found by using typeof on the type parameter you give to Resolve.

If the type cannot be constructed by new (because it's in some unknown separate codebase) then you wouldn't be able to give it as a type parameter to Resolve.

In the second case, it is constructor injection, but it's still a known concrete constructable type. Via reflection, the Unity framework can get an array of all the Types of the parameters to the constructor. The type TestWhatever is constructable, so there is no ambiguity or difficulty over what to construct.

As to your concern about separate modules (assemblies), if you move TestWhatever to another assembly, that will not change the lines of code you've written; it will just mean that you have to add a reference to the other assembly to get this one to build. And then TestWhatever is still an unambiguously refeferenced constructable type, so it can be constructed by Unity.

In other words, if you can refer to the type in code, you can get a Type object, and so at runtime it will be directly constructable.

Response to comment:

If you delete the class TestWhatever, you will get a compile-time error, because you refer to that type in your code. So it won't be possible to get a runtime by doing that.

The decoupling is still in effect in this arrangement, because you could register a specific instance of TestWhatever, so every call to Resolve<TestWhatever>() will get the same instance, rather than constructing a new one.

Daniel Earwicker
But then where does the decoupling come in, i.e. if I need a class "MenuManager" from another module, and that module doesn't happen to be loaded, I understand that the container should e.g. inject a null so that the application can function with or without other parts, but if I e.g. delete the class "TestWhatever" the application get an error.
Edward Tanguay
Think about it: how are you going to specify that MenuManager is to be created?
Daniel Earwicker
Before the CLR execute an expression like `Resolve<MenuManager>();` it has to load the assembly containing the definition of MenuManager. If you refer to a type in your code, you force the loading of the assembly containing that type definition.
Daniel Earwicker
It would be nice if when I created a class in any module, I could define whatever I needed in my constructor, e.g. ...(MenuManager menuManager, ...) and if it happened to exist I would get it, if not I would get a null, I would have a if(menuManager != null) and things would stay decoupled, but when I delete TestWhatever, the application gets an error, which seems to be an example of coupling instead of decoupling, or what am I missing?
Edward Tanguay
But you're specifying a concrete constructable type. That - by definition - is not decoupled. To get decoupling, specify the `interface` you need the object to satisfy, instead of a specific constructable class.
Daniel Earwicker
ok, but even in the case of an Interface it seemed coupled, e.g. when I comment out "container.RegisterType<ICustomersRepository, CustomersRepository>(new ContainerControlledLifetimeManager());" I get a "cannot resolve dependency" on the constructor which has the parameter "ICustomersRepository customersRepository". If I need to make sure that all the objects that I'm requesting in my constructor actually exist, then the Prism architecture is less decoupled than I thought. So I guess the decoupling comes from the fact that I can request a parameter "IMenuManager menuManager" and know that
Edward Tanguay
The neatest way to have an optional component is by using `IFoo` (an interface) to specify what you need, and then configuring the system to use some `NullFoo : IFoo` which would be the dummy implementation that does "nothing" (or as close to nothing as makes sense for `IFoo`).
Daniel Earwicker
...(cont.) at least I will get some menuManager object which satisfies IMenuManager. But I have to make sure all of these objects actually exist, even in other modules. I just that that was something that the container would blackbox as well, since e.g. I remember seeing Prism demos (StockTrader) where I commented out an AddModule line, and the application still ran, but without one area of the screen where that module would have been. But if other modules refer to objects in that module, then the application will break.
Edward Tanguay
ok yes the Null pattern is nice here, that makes sense.
Edward Tanguay
That's not coupling, that's configuration. When you register the class that satisfies a given interface, you're configuring the system for your present needs. But you aren't baking a dependency into the components themselves (they only know about the interfaces). And even a component does `Resolve<SomeClass>` it's still possible to use configuration on the outside to control what is returned.
Daniel Earwicker
this convo was very helpful, thanks!
Edward Tanguay
One option is the NullPattern, the other is to use property dependencies for the optional imports as properties are always optional. In MEF we allow you to have optional imports which are set to Null. Maybe soemone should post on the Unity forums a suggestion to support optional dependencies.
Glenn Block
+1  A: 

The reason this works is because Unity is designed for it. When you Resolve with a concrete type, Unity looks to see if it can resolve from the container. If it cannot, then it just goes and instantiates the type resolving it's dependencies. It's really quite simple.

Glenn Block