本文示例代碼,均採用 .NET 6,具體的代碼可以在這個倉庫 Articles.DI 中獲取。
在 .NET 中的依賴關係注入是一等公民,官方框架了提供配置、日誌記錄和選項等模式。
依賴注入(DI)通過下面的方式,解決了前面的這些問題:
- 使用介面或基類,將依賴關係實現抽象化;
 - 使用統一容器註冊服務;
 - 將服務注入到使用它的類中,由框架負責服務的實例化,且由框架管理它的聲明週期;
 
我們用一個示例,來展示如何在 .NET 中,使用依賴注入。下面這個示例是一個後臺服務Worker,其依賴MessageWriter,每隔 1s,調用 MessageWriter.Write 輸出一行簡單的日誌。
// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService2
// 定義 host 服務
IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
   .Build();
await host.RunAsync();  // 啟動
// 具體的後臺服務類
public class Worker : BackgroundService
{
    // 硬編碼依賴,需要自行初始化成員變量
    private readonly MessageWriter _messageWriter = new();
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}
public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}
使用依賴注入#
前面的代碼中,Worker類顯式依賴MessageWriter類,且在自己的代碼塊中,自己實例化了對應實例。
隨著項目的複雜度增加,Worker類包含多個不同變量,那麼都要Worker類自行實例化,而且若是這些變量又依賴其他服務,那麼光是初始化的代碼就會非常的繁雜。並且後續不使用MessageWriter類,想換一種實現,那麼也要修改代碼,兩個類之間的耦合非常緊密。
為了解開二者之間的耦合,我們根據下面的幾個步驟,使用依賴注入,做到控制反轉:
- 將
MessageWriter類的行為抽象出一個介面IMessageWriter; - 在容器中註冊服務,將
IMessageWriter的實現註冊為MessageWriter; - 將
_messageWriter的類型修改為介面IMessageWriter,Worker類只依賴介面;
4.Worker類在構造函數中,聲明需要的類型變量,由容器進行注入; 
IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
        {
            services.AddHostedService<Worker>();
            // 2. 註冊服務
            services.AddSingleton<IMessageWriter, MessageWriter>();
        })
    .Build();
await host.RunAsync();
// 1. 抽象介面
public interface IMessageWriter
{
    void Write(string message);
}
public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}
public class Worker : BackgroundService
{
    // 3. 鬆耦合,只依賴介面,不依賴具體實現。Worker 類不負責初始化成員
    private readonly IMessageWriter _messageWriter;
    // 4. 從構造函數傳遞注入具體的實例,將其賦值給 _messageWriter 成員
    public Worker(IMessageWriter messageWriter)
    {
        _messageWriter = messageWriter;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}
上述代碼中,Worker類和MessageWriter類之間並沒有直接聯繫,前者擁有一個IMessageWriter類型的成員,容器服務會通過構造函數注入一個實例進來,Worker類可以直接使用,而無需知道如何構造一個該類型的實例。後者MessageWriter是一個實現IMessageWriter介面的類,它只需要根據介面要求,實現對應方法,不用去管其他人員會如何使用它,而在容器註冊服務時,將MessageWriter註冊為IMessageWriter的實現,那麼在有服務依賴於IMessageWriter時,容器會自動生成一個MessageWriter實例注入進去。
依賴注入將兩個類解耦,後續如果想要更換IMessageWriter的具體實現,可以直接添加一個新的實現類,然後修改註冊即可,可以參考下面的代碼:
public class LoggingMessageWriter : IMessageWriter
{
    private readonly ILogger<LoggingMessageWriter> _logger;
    public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger)
    {
        _logger = logger;
    }
    public void Write(string message)
    {
        _logger.LogInformation(message);
    }
}
// 註冊服務: ConfigureServices 代碼塊
// services.AddSingleton<IMessageWriter, MessageWriter>();
// 在註冊服務時更改 IMessageWriter 的實現,無需修改每一處業務代碼
services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
Worker類只依賴於IMessageWriter介面,不去關注如何實現,如何生成,這些事情由框架去做。我們要做的只是重新寫一個實現類,然後修改註冊服務的代碼即可。