1
votes

I'm trying to get to grips with IoC containers (Unity specifically at the moment), and am having a bit of struggle with the concept of injecting all dependencies up front.

My problem is specifically related to a class that has a constructor parameter that has a value that isn't known when I initially register the types in the container.

Here's a little snippet that should illustrate what I'm talking about.

class Class1
{
    IUnityContainer uContainer;

    public Class1()
    {
        uContainer = new UnityContainer();

        uContainer.RegisterType<IRepo, Repo>(new ContainerControlledLifetimeManager()));

        Class2 cls2 = uContainer.Resolve<Class2>();

        cls2.DoSomething();

    }
}

class Class2
{
    IRepo _repo;

    public Class2(IRepo p_repo)
    {
        _repo = p_repo;
    }

    public void DoSomething()
    {
        IType2 typ2 = new Type2(_repo.SomeDataRetrieved());

        Class3 cls3 = new Class3(_repo, typ2);
    }
}

class Class3
{
    IRepo _repo;
    IType2 _type2;

    public Class3(IRepo p_repo, IType2 p_type2)
    {
        _repo = p_repo;
        _type2 = p_type2;
    }
}

I can set up the container in Class1 and have the Repo injected into Class2 using the UnityContainer. In Class2, some lookup against the Repo is done returning an instance of Type2 that is only possible to instantiate in Class2. I then need to pass the Repo to Class3 together with the new object created of Type2.

The problem is twofold:

  1. I can't resolve Class3 in Class1 because it can only be instantiated in Class2.
  2. I can't register Type2 in the container because it actually has to have a value (not a default one) based on the output of a call in Class2, which is in a different scope from where the Container existed and did the registrations.

As it stands, I can inject the dependancies using the container into Class2, but for Class3 I need to create new instances or using contructor injection from 2 to 3, which then makes me wonder why I'm using the Container then if I have to resort to manual injection anyway.

So, how do I register Class3 in the container so that when I instantiate it, I Inject the singleton of the repo that was registered in the container at the beginning as well as the instance of Type2 that was created in DoSomething.

Thanks in advance for the help.

2

2 Answers

0
votes

I see multiple things about your approach:

  1. The container and registration should occur before being in Class1. If you just wrote it like that as an exemple, and that in your actual code, it's in the program, or equivalent, disregard this comment.
  2. You should look into the factories with Unity. For example, when constructing Class2, you could pass a Func<IType2, Class3> to it's constructor, and from within Class2, call that factory to get the Class3 instance. Here is an example :
container.RegisterType<Func<Type2, Class3>>(
    new InjectionFactory(c =>
    new Func<Type2, Class3>(type2 =>
    c.Resolve<Class3>(
        new InjectionConstructor(
            new ResolvedParameter<IRepo>(),
            type2)))));

Note: Haven't tested that code, but I've done something similar.

0
votes

In addition to Tipx's answer, when doing dependency injection, you don't want your classes to directly create other classes.

public void DoSomething()
{
    IType2 typ2 = new Type2(_repo.SomeDataRetrieved());
    Class3 cls3 = new Class3(_repo, typ2);
}

This ties class2 to Type2 and Class3, which might be ok if those three are that close that none one of them can be exchanged, but normally at some point in time you want to swap out Type2 for OverchargedType2Plus or Type2Mock. And that's what the abstract factory is for: give Class2 something that can create Type2s.

interface IType2Factory
{
     IType2 Create( Data theData );
}

class Type2Type2Factory : IType2Factory
{
     public IType2 Create( Data theData ) => new Type2( theData );
}

(If Type2 needs further dependencies, see this answer)

And then, give the IType2Factoryto Class2:

class Class2
{
    private readonly IRepo _repo;
    private readonly IType2Factory _type2Factory;

    public Class2( IRepo repo, IType2Factory type2Factory )
    {
        _repo = repo;
        _type2Factory = type2Factory;
    }

    public void DoSomething()
    {
        var typ2 = _type2Factory.Create( _repo.SomeDataRetrieved() );
    }
}

And when the day comes and you need OverchargedType2Plus, create an IType2Factory implementation and register that instead and watch Class2 work with the new Type2 variant without even knowning it exists, let alone requiring any changes.