7

I'm facing a rather puzzling situation with DryIoC.

OK, actually, this is the first time I use an IoC container, so I may just be misunderstanding everything: from dependency injection, to IoC containers, to DryIoC itself.

Still, I've been a professional programmer for quite some time, I have decent Googling skills, and I couldn't even find a similar problem exposed by someone else.

Let's say I have a class library which exposes these interfaces...

namespace Logging
{
    public interface ILogger
    {
        void Info(string message);
    }

    public interface ILoggerFactory
    {
        ILogger CreateLogger(string name);
    }
}

...and another class library implementing the above interfaces.

namespace Logging.Console
{
    internal class ConsoleLogger : ILogger
    {
        readonly string _name;

        public ConsoleLogger(string name)
        {
            _name = name;
        }

        void Info(string message) => System.Console.WriteLine($"[{_name}] {message}");
    }

    public class ConsoleLoggerFactory : ILoggerFactory
    {
        public ILogger CreateLogger(string name) => new ConsoleLogger(name);
    }
}

Then a third library with other stuff I need:

namespace LibraryA
{
    public interface IServiceA
    {
        // bla bla
    }

    public class ServiceA : IServiceA
    {
        // Implement service A
    }

    public interface IServiceB
    {
        // yada yada
    }

    public class ServiceB : IServiceB
    {
        // Implement service B
    }
}

Finally, a class library using all libraries above to implement a coffee grinder (I love coffee!)

using Logging;
using LibraryA;

namespace Coffee
{
    public interface ICoffeeGrinder
    {
        GrindCoffee(int grams);
    }

    public class CoffeeGrinder : ICoffeeGrinder
    {
        readonly ILogger _logger;
        readonly IServiceA _serviceA;
        readonly IServiceB _serviceB;

        public CoffeeGrinder(ILoggerFactory loggerFactory, string loggerName,
            IServiceA serviceA, IServiceB serviceB)
        {
            _logger = loggerFactory.CreateLogger(loggerName);
            _serviceA = serviceA;
            _serviceB = serviceB;
        }

        public GrindCoffee(int grams)
        {
            _logger.Info($"About to grind {grams}g of coffee...");
            // Grind coffee
            _logger.Info("Done grinding.");
        }
    }
}

An application may need more than one grinder, each with its own name, according to (for example) a configuration file.

Therefore I want to be able to specify loggerName at resolve time, like this:

using Coffee;
using DryIoC;
using LibraryA;
using Logging;
using Logging.Console;

namespace MyApplication
{
    class Program
    {
        static void Main()
        {
            using (var container = new Container())
            {
                container.Register<ILoggerFactory, ConsoleLoggerFactory>();
                container.Register<IServiceA, ServiceA>();
                container.Register<IServiceB, ServiceB>();
                container.Register<ICoffeeGrinder, CoffeeGrinder>(
                    /* Maybe some magic here? */);

                // This won't work
                var grinder = container.Resolve<ICoffeeGrinder>("My grinder"); 
                // Use grinder
            }
        }
    }
}

In other words, how do I tell DryIoC that one or more constructor parameters are not dependencies, but must be specified at resolve time instead?

Steven
  • 166,672
  • 24
  • 332
  • 435
rdeago
  • 136
  • 2
  • 8
  • 1
    I would like to question whether you really want to do that. In their very nature, constructor parameters are a dependency as well, they're a dependency on the concrete implementation of the underlying class. That string you want to pass in, what is the meaning of that string in a completely different service that has completely different implementation semantics? Should it be an url to something? A filename and path? The name of something? You're still leaking concrete implementation details out into the code that consumes the services. In my opinion, you should not do that. – Lasse V. Karlsen Mar 11 '17 at 11:49
  • @LasseV.Karlsen the string in question is just a name, used to identify an object in logs. I don't think that counts as leaking implementation details. `CoffeeGrinder`'s constructor could take an `ILogger` but it should be specified at resolve time as well, so the problem would be the same. Then again, I could just be catastrophically wrong to write a constructor like that in the first place. I just fail to see any better way to have my classes use a logger whose name is not known to the class but is known to the code calling the constructor. – rdeago Mar 11 '17 at 14:25
  • I'm pretty sure is is possible but unfortunately I'm not at my computer tonight. See if there is a `with` parameter to the Resolve method. – Lasse V. Karlsen Mar 11 '17 at 18:54

1 Answers1

14

Resolve as Func<string, ICoffeeGrinder>, this is not uncommon feature supported by major containers, e.g. Autofac. Here is DryIoc wiki on this topic.

var getGrinder = container.Resolve<Func<string, ICoffeeGrinder>>();
var grinder = getGrinder(name);

Btw, you may look into major feature list of many IoC/DI containers to save your time. This link is also available on DryIoc readme under Benchmarks.

Markus Hütter
  • 7,796
  • 1
  • 36
  • 63
dadhi
  • 4,807
  • 19
  • 25
  • 2
    Thanks a lot @dadhi ! That's exactly what I was looking for. I don't know how it escaped me when I read the documentation (because yes, I did read it). Information overflow, probably. Aargh, I'm getting old... :-) – rdeago Mar 12 '17 at 07:40