Skip to content

Direct Dependencies & Enumeration<T>

Last updated on July 16, 2018

In this post, we will be talking about the two types of relationships, namely, direct dependency relationship and enumeration. In terms of a component A and service B, these relationships can be stated as the following:

  • Direct Dependency – A needs B
  • Enumeration – A needs all the kinds of B

 

Direct Dependency (B)

It’s quite common to have a direct dependency relationship –  component A needs service B. In this case, the code for A would look like the following:

class A
{
    private B _b;
    public A(B b)
    {
        _b = b;
    }

    public void UseService()
    {
        _b.ServiceMethod();
    }
}

 

In order to make the above code work, all we need is to register the component and the service with your container. And then resolve as:

var builder = new ContainerBuilder();
builder.RegisterType<A>();
builder.RegisterType<B>();

var container = builder.Build();
using(var scope = container.BeginLifetimeScope())
{
  // B is automatically injected into A.
  var a = scope.Resolve<A>();
}

 

Enumeration (IEnumerable<B>, IList<B>, ICollection<B>)

When we inject a dependency of enumeration type, the DI container returns all the implementations of the same service. This is useful in cases like error loggers, where an event (an application error for instance) occurs and there are more than one logger registered to log the event.

IEnumerable<T>
IEnumerable

Let’s say we have a service dependency interface as IAuditLogger defined as:

public interface IAuditLogger
{
  void LogMessage(string message);
}

 

Further, the consumer of this service ErrorHandler, uses all the implementations of IAuditLogger to log any errors:

public class ErrorHandler
{
  private readonly IEnumerable<IAuditLogger> _loggers;
  public ErrorHandler(IEnumerable<IAuditLogger> loggers)
  {
    _loggers = loggers;
  }

  public void LogErrorMessage(string message)
  {
    foreach(var logger in _loggers)
    {
      logger.LogMessage(message);
    }
  }
}

 

In order to make the above code work, all we need is to register all the implementations of IAuditLogger with the container. As a result, when we resolve the consumer, all the matching dependencies will be resolved as an enumerable.

var builder = new ContainerBuilder();
builder.RegisterType<TextLogger>().As<IAuditLogger>();
builder.RegisterType<DbLogger>().As<IAuditLogger>();
builder.RegisterType<ConsoleLogger>().As<IAuditLogger>();
builder.RegisterType<ErrorHandler>();

var container = builder.Build();
using(var scope = container.BeginLifetimeScope())
{
  // When ErrorHandler is resolved, it will have all of 
  // the registered loggers injected to its constructor
  var errorHandler = scope.Resolve<ErrorHandler>();
  errorHandler.LogErrorMessage("some error message");
}

 

It’s equally important to note that the container will return an empty list if no matching items are registered in the container. Therefore, in the above example, if we don’t register any implementation of IAuditLogger then we will get an empty list.

Published inDesign Patterns & Principles