Using Dependency Injection frameworks for classes with many dependencies

First:

You could inject these objects, when needed, as members instead of in the constructor. That way you don't have to make changes to the constructor as your usage changes, and you also don't need to use a static.

Second:

Pass in some sort of builder or factory.

Third:

Any class should only have those dependencies that it itself requires. Subclasses should be injected with their own specific dependencies.


I have a similar case related to the "expensive to create and might be used", where in my own IoC implementation, I'm adding automagic support for factory services.

Basically, instead of this:

public SomeService(ICDBurner burner)
{
}

you would do this:

public SomeService(IServiceFactory<ICDBurner> burnerFactory)
{
}

ICDBurner burner = burnerFactory.Create();

This has two advantages:

  • Behind the scenes, the service container that resolved your service is also used to resolve the burner, if and when it is requested
  • This alleviates the concerns I've seen before in this kind of case where the typical way would be to inject the service container itself as a parameter to your service, basically saying "This service requires other services, but I'm not going to easily tell you which ones"

The factory object is rather easy to make, and solves a lot of problems.

Here's my factory class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LVK.IoC.Interfaces;
using System.Diagnostics;

namespace LVK.IoC
{
    /// <summary>
    /// This class is used to implement <see cref="IServiceFactory{T}"/> for all
    /// services automatically.
    /// </summary>
    [DebuggerDisplay("AutoServiceFactory (Type={typeof(T)}, Policy={Policy})")]
    internal class AutoServiceFactory<T> : ServiceBase, IServiceFactory<T>
    {
        #region Private Fields

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly String _Policy;

        #endregion

        #region Construction & Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <param name="policy">The policy to use when resolving the service.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer, String policy)
            : base(serviceContainer)
        {
            _Policy = policy;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer)
            : this(serviceContainer, null)
        {
            // Do nothing here
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the policy that will be used when the service is resolved.
        /// </summary>
        public String Policy
        {
            get
            {
                return _Policy;
            }
        }

        #endregion

        #region IServiceFactory<T> Members

        /// <summary>
        /// Constructs a new service of the correct type and returns it.
        /// </summary>
        /// <returns>The created service.</returns>
        public IService<T> Create()
        {
            return MyServiceContainer.Resolve<T>(_Policy);
        }

        #endregion
    }
}

Basically, when I build the service container from my service container builder class, all service registrations are automatically given another co-service, implementing IServiceFactory for that service, unless the programmer has explicitly registered on him/her-self for that service. The above service is then used, with one parameter specifying the policy (which can be null if policies aren't used).

This allows me to do this:

var builder = new ServiceContainerBuilder();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>();

using (var container = builder.Build())
{
    using (var factory = container.Resolve<IServiceFactory<ISomeService>>())
    {
        using (var service = factory.Instance.Create())
        {
            service.Instance.DoSomethingAwesomeHere();
        }
    }
}

Of course, a more typical use would be with your CD Burner object. In the above code I would resolve the service instead of course, but it's an illustration of what happens.

So with your cd burner service instead:

var builder = new ServiceContainerBuilder();
builder.Register<ICDBurner>()
    .From.ConcreteType<CDBurner>();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>(); // constructor used in the top of answer

using (var container = builder.Build())
{
    using (var service = container.Resolve<ISomeService>())
    {
        service.Instance.DoSomethingHere();
    }
}

inside the service, you could now have a service, a factory service, which knows how to resolve your cd burner service upon request. This is useful for the following reasons:

  • You might want to resolve more than one service at the same time (burn two discs simultaneously?)
  • You might not need it, and it could be costly to create, so you only resolve it if needed
  • You might need to resolve, dispose, resolve, dispose, multiple times, instead of hoping/trying to clean up an existing service instance
  • You're also flagging in your constructor which services you need and which ones you might need

Here's two at the same time:

using (var service1 = container.Resolve<ISomeService>())
using (var service2 = container.Resolve<ISomeService>())
{
    service1.Instance.DoSomethingHere();
    service2.Instance.DoSomethingHere();
}

Here's two after each other, not reusing the same service:

using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingHere();
}
using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingElseHere();
}

Well, while you can do this as described in other answers I believe there is more important thing to be answered regarding your example and that is that you are probably violating SRP principle with class having many dependencies.

What I would consider in your example is breaking up the class in couple of more coherent classes with focused concerns and thus the number of their dependencies would fall down.

Nikola's law of SRP and DI

"Any class having more than 3 dependencies should be questioned for SRP violation"

(To avoid lengthy answer, I posted in detail my answers on IoC and SRP blog post)


First: Add the simple dependencies to your constructor as needed. There is no need to add every type to every constructor, just add the ones you need. Need another one, just expand the constructor. Performance should not be a big thing as most of these types are likely to be singletons so already created after the first call. Do not use a static DI Container to create other objects. Instead add the DI Container to itself so it can resolve itself as a dependency. So something like this (assuming Unity for the moment)

IUnityContainer container = new UnityContainer();
container.RegisterInstance<IUnityContainer>(container);

This way you can just add a dependency on IUnityContainer and use that to create expensive or seldom needed objects. The main advantage is that it is much easier when unit testing as there are no static dependencies.

Second: No need to pass in a factory class. Using the technique above you can use the DI container itself to create expensive objects when needed.

Three: Add the DI container and the light singleton dependencies to the main form and create the rest through the DI container as needed. Takes a little more code but as you said the startup cost and memory consumption of the mainform would go through the roof if you create everything at startup time.