In my application I regularly want to write log messages to disk. I created a simple logger class and it's constructed using Dependency Injection, as follows:
var logger = new LogService(new FileLogger());
logger.WriteLine("message");
But this now means that every class in my system that needs to log, needs to get this LogService
injected into it, which seems redundant. I, therefore, like to make the LogService
static instead. This way it doesn't need to be injected into consuming classes.
But here comes the problem. If I make a logger class static, there is no way to inject dependency by constructor into that static class.
So, I changed my LogService
to this.
public static class LogService()
{
private static readonly ILoggable _logger;
static LogService()
{
_logger = new FileLogger();
}
public static void WriteLine(string message) ...
}
This feels weird to me. I think this is not DI anymore.
What's the best way for me to inject dependencies into a static class?
Dependency Injection, as a practice, is meant to introduce abstractions (or seams) to decouple volatile dependencies. A volatile dependency is a class or module that, among other things, can contain nondeterministic behavior or in general is something you wish to be able to replace or intercept.
For a more detailed discussion about volatile dependencies, see section 1.3.2 of this freely readable introduction of my book.
Because your FileLogger
writes to disk, it contains nondeterministic behavior. For this reason you introduced the ILoggable
abstraction. This allows consumers to be decoupled from the FileLogger
implementation, and allows you — later on — when such requirement comes in, to easily swap this FileLogger
implementation with a SqlLogger
implementation that logs to a SQL database, or even have an implementation that forwards the call to both the FileLogger
and the SqlLogger
.
To be able to successfully decouple a consumer from its volatile dependency, however, you need to inject that dependency into the consumer. There are three common patterns to choose from:
Both Constructor Injection and Property Injection are applied inside the startup path of the application (a.k.a. the Composition Root) and require the consumer to store the dependency in a private field for later reuse. This requires the constructor and property to be instance members, i.e. non-static. Constructor Injection is typically preferred over Property Injection, because Property Injection leads to Temporal Coupling. Static constructors can't have any parameters and static properties lead to the Ambient Context anti-pattern (see section 5.3)—this hinders testability and maintainability.
Method injection, on the other hand, is applied outside the Composition Root and it does not store any supplied dependency, but instead merely uses it. Here's an example from the earlier reference:
// This method calculates the discount based on the logged in user.
// The IUserContext dependency is injected using Method Injection.
public static decimal CalculateDiscountPrice(decimal price, IUserContext context)
{
// Note that IUserContext is never stored - only used.
if (context == null) throw new ArgumentNullException("context");
decimal discount = context.IsInRole(Role.PreferredCustomer) ? .95m : 1;
return price * discount;
}
Method injection is, therefore, the only of the three patterns that can be applied to both instance and static classes.
When applying Method Injection, the method's consumer must supply the dependency. This does mean, however, that the consumer itself must have been supplied with that dependency either through Constructor, Property, or Method Injection. For example:
public class ProductServices : IProductServices
{
private readonly IProductRepository repository;
private readonly IUserContext userContext;
public ProductServices(
IProductRepository repository,
IUserContext userContext) // <-- Dependency applied using Ctor Injection
{
this.repository = repository;
this.userContext = userContext;
}
public decimal CalculateCustomerProductPrice(Guid productId)
{
var product = this.repository.GetById(productId);
return CalculationHelpers.CalculateDiscountPrice(
product.Price,
this.userContext); // <-- Dep forwarded using Method Injection
}
}
Your example of the static LogService
that created FileLogger
inside its constructor is a great example of tightly coupled code. This is known as the Control Freak anti-pattern (section 5.1) or in general can be seen as a DIP violation—this is the opposite of DI.
To prevent tight coupling of volatile dependencies, the best is to make LogService
non-static and inject its volatile dependencies into its sole public constructor:
public class LogService
{
private readonly ILoggable _logger;
public LogService(ILoggable logger)
{
_logger = logger;
}
public void WriteLine(string message) ...
}
This likely defeats the purpose of your LogService
class, because now consumers would be better off by injecting ILoggable
directly instead of injecting LogService
. But this brings you back to the reason why you probably wanted to make that class static in the first place, which is that you have many classes that need to log and it feels cumbersome to inject ILoggable
into all those constructors.
This, however, might be caused by another design issue in your code. To understand this, you might want to read through this q&a to get some sense of what design changes you can make that allows less classes to depend on your logger class.