简体   繁体   English

如何将依赖项注入到 static class

[英]How to inject dependency to static class

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:我创建了一个简单的记录器 class 并使用依赖注入构建,如下所示:

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.但这现在意味着我系统中需要记录的每个 class 都需要将这个LogService注入其中,这似乎是多余的。 I, therefore, like to make the LogService static instead.因此,我想改为使用LogService static。 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.如果我制作了一个记录器 class static,则无法通过构造函数将依赖项注入该 static ZA2F21A9F8EBC2CBCBDC4。

So, I changed my LogService to this.所以,我把我的LogService了这个。

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.我认为这不再是 DI。

What's the best way for me to inject dependencies into a static class?我将依赖项注入 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 which 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 .有关 volatile 依赖项的更详细讨论,请参阅我的书的这个可自由阅读的介绍第 1.3.2 节

Because your FileLogger writes to disk, it contains nondeterministic behavior .因为您的FileLogger写入磁盘,所以它包含非确定性行为 For this reason you introduced the ILoggable abstraction.为此,您引入了ILoggable抽象。 This allows consumers to be decoupled from the FileLogger implementation.这允许消费者与FileLogger实现分离。

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 (aka 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 , ie non-static.这要求构造函数和属性是实例成员,即非静态的。 Static constructors can't have any parameters and static properties lead to the Ambient Context anti-pattern (see section 5.3), and Temporal Coupling .静态构造函数不能有任何参数,静态属性导致Ambient Context 反模式(参见第 5.3 节)和Temporal Coupling 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.方法注入,另一方面,在施加的组合物根,它存储任何提供的相关性,而仅仅使用它。

Method injection is, therefore, the only of the three patterns that can be applied to both instance and static methods.因此,方法注入是三种模式中唯一可以同时应用于实例方法和静态方法的模式。

In that case, 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.然而,这确实意味着必须通过构造函数、属性或方法注入为使用者本身提供该依赖项。

Your example of the static LogService that created FileLogger inside its constructor is a great example of tightly coupled code.您在其构造函数中创建FileLogger的静态LogService示例是紧密耦合代码的一个很好的示例。 This is known as the Control Freak anti-pattern (section 5.1) or in general can be seen as a DIP violation .这被称为 Control Freak 反模式(第 5.1 节),或者通常可以视为DIP 违规 This is the opposite of DI.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.为了防止 volatile 依赖的紧耦合,最好的方法是使LogService非静态并将其 volatile 依赖注入到其唯一的公共构造函数中。

It doesn't make sense to use dependency injection (DI) with a static class.将依赖注入 (DI) 与静态类一起使用是没有意义的。 Instead of DI, simply add an initialization method to your static class and pass in the dependency.而不是 DI,只需向您的静态类添加一个初始化方法并传入依赖项。

public static class LogService
{
    private static ILoggable _logger;

    public static ILoggable Logger
    {
        get
        {
             return _logger;
        }
    }

    public static void InitLogger(ILoggable logger)
    {
         _logger = logger;
    }
}

To use the logger, just make sure to call InitLogger() first:要使用记录器,请确保首先调用InitLogger()

LogService.InitLogger(new FileLogger());
LogService.Logger.WriteLine("message");

You could use Lazy initialization for any object you need to inject to a static class.您可以对需要注入到 static class 的任何 object 使用延迟初始化。

https://docs.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-5.0 https://docs.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-5.0

This would allow you to pass around static objects that can be shared across running instances and other classes/methods that need to use those objects.这将允许您传递 static 对象,这些对象可以在运行的实例和需要使用这些对象的其他类/方法之间共享。 An example would be an HttpClient that you want to share across your entire application.一个示例是您希望在整个应用程序中共享的 HttpClient。 You can lazy initialize the HttpClient inside of a static class and refer to the static class to get the HttpClient.您可以在 static class 中延迟初始化 HttpClient 并参考 static ZA2F2ED4F8EBC2CBB4C2 获取 HttpClient

Here's another example using a CosmosDB client: https://docs.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients这是使用 CosmosDB 客户端的另一个示例: https://docs.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients

Here's an example of injecting log4net loggers with Unity DI container using UnityLog4NetResolver extension: 以下是使用UnityLog4NetResolver扩展程序使用Unity DI容器注入log4net记录器的示例:

public class A
{
    readonly ILog log;

    public A(ILog log)
    {
        this.log = log;
    }

    ...
}

...

var container = new UnityContainer();

container
    .AddNewExtension<UnityLog4NetResolver.LogByTypeResolverExtension>();

container.Resolve<A>();

The last statement is equivalent to new A(LogManager.GetLogger(typeof(A))) . 最后一个语句相当于new A(LogManager.GetLogger(typeof(A))) Thus each class can have a logger with a corresponding name injected by the DI container. 因此,每个类可以具有由DI容器注入的具有相应名称的记录器。

The idea was taken from this package . 这个想法来自这个方案 However, it seems to be abandoned and does not support last Unity version. 但是,它似乎被放弃了,并且不支持上一个Unity版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM