Direct Dependencies And Enumeration


In this post, we will be talking about the two most commonly used 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.
  1. class A  
  2. {  
  3.     private B _b;  
  4.     public A(B b)  
  5.     {  
  6.         _b = b;  
  7.     }  
  9.     public void UseService()  
  10.     {  
  11.         _b.ServiceMethod();  
  12.     }  
  13. }  
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 -
  1. var builder = new ContainerBuilder();  
  2. builder.RegisterType<A>();  
  3. builder.RegisterType<B>();  
  5. var container = builder.Build();  
  6. using(var scope = container.BeginLifetimeScope())  
  7. {  
  8.   // B is automatically injected into A.  
  9.   var a = scope.Resolve<A>();  
  10. }  
Enumeration (IEnumerable<B>, IList<B>, ICollection<B>)
When we inject a dependency of the 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 loggers registered to log the event.

Let's say we have a service dependency interface as IAuditLogger defined as -
  1. public interface IAuditLogger  
  2. {  
  3.   void LogMessage(string message);  
  4. }  

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

  1. public class ErrorHandler  
  2. {  
  3.   private readonly IEnumerable<IAuditLogger> _loggers;  
  4.   public ErrorHandler(IEnumerable<IAuditLogger> loggers)  
  5.   {  
  6.     _loggers = loggers;  
  7.   }  
  9.   public void LogErrorMessage(string message)  
  10.   {  
  11.     foreach(var logger in _loggers)  
  12.     {  
  13.       logger.LogMessage(message);  
  14.     }  
  15.   }  
  16. }  

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.

  1. var builder = new ContainerBuilder();    
  2. builder.RegisterType<TextLogger>().As<IAuditLogger>();    
  3. builder.RegisterType<DbLogger>().As<IAuditLogger>();    
  4. builder.RegisterType<ConsoleLogger>().As<IAuditLogger>();    
  6. var container = builder.Build();    
  7. using(var scope = container.BeginLifetimeScope())    
  8. {    
  9.   // When ErrorHandler is resolved, it will have all of     
  10.   // the registered loggers injected to its constructor    
  11.   var errorHandler = scope.Resolve<ErrorHandler>();    
  12.   errorHandler.LogErrorMessage("some error message");    
  13. }     

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.

There is n number of scenarios in which these relationships can be used in dependency injection. In fact, we see them every day somewhere in the projects that we are working on. However, there is a lot more to it, and we will explore more relationship types in upcoming posts.

Related Articles