【C#】Dependency Injection（依存性の注入）とは？

## はじめに

ソフトウェア設計を学び始めると、「依存性の注入」「Dependency Injection（DI）」「DIコンテナ」などの用語に出会います。
これらは、柔軟で保守性の高いコードを書くために重要な概念です。
この記事では、C#を例に、Dependency Injectionの基本から、サービスロケーターやDIコンテナについて学んだことを備忘録としてまとめています。
初学者ですので、間違いなどございましたらコメントでご指摘いただけますと幸いです。

## Dependency Injection（DI）とは？

Dependency Injection（依存性の注入）とは、オブジェクトが依存する他のオブジェクトを外部から提供（注入）してもらう設計パターンです。これにより、オブジェクト同士の結合度を下げ、柔軟でテストしやすいコードを実現できます。

### 例：依存性の注入を行わない場合

以下の`FooService`クラスは、`Logger`クラスに直接依存しています。

```csharp
public class Logger
{
    public void Write(string message)
    {
        Console.WriteLine("Message: " + message);
    }
}

public class FooService
{
    private readonly Logger _logger = new();

    public void Execute()
    {
        _logger.Write("Foo!");
    }
}
```

この実装では、`FooService`が`Logger`を直接生成しているため、以下の問題があります：

- **柔軟性の欠如**：`Logger`の実装を変更するには、`FooService`のコードも変更する必要があります。
- **テストの困難さ**：`Logger`をモックに差し替えることができないため、`FooService`の単体テストが難しくなります。

### 例：依存性の注入を行う場合

依存性の注入を行うことで、上記の問題を解決できます。

```csharp
public interface ILogger
{
    void Write(string message);
}

public class Logger : ILogger
{
    public void Write(string message)
    {
        Console.WriteLine("Message: " + message);
    }
}

public class FooService
{
    private readonly ILogger _logger;

    public FooService(ILogger logger)
    {
        _logger = logger;
    }

    public void Execute()
    {
        _logger.Write("Foo!");
    }
}
```

この実装では、`FooService`は`ILogger`インターフェースに依存しており、具体的な実装は外部から注入されます。これにより、`Logger`の実装を変更しても、`FooService`のコードを変更する必要がなくなります。また、テスト時にはモックの`ILogger`を注入することで、`FooService`の単体テストが容易になります。

## 依存性の注入の実現方法

### 手動での注入

小規模なアプリケーションでは、手動で依存性を注入することが可能です。

```csharp
var logger = new Logger();
var fooService = new FooService(logger);
fooService.Execute();
```

しかし、アプリケーションが大規模になると、依存関係の管理が複雑になり、手動での注入が困難になります。

### サービスロケーター

サービスロケーターは、依存性を解決するための仕組みの一つです。以下は、簡易的なサービスロケーターの実装例です：

```csharp
using System;
using System.Collections.Generic;

public static class ServiceLocator
{
    private static readonly Dictionary<Type, object> _services = new();

    public static void Register<T>(T service)
    {
        _services[typeof(T)] = service;
    }

    public static T Resolve<T>()
    {
        return (T)_services[typeof(T)];
    }
}
```

使用例：

```csharp
ServiceLocator.Register<ILogger>(new Logger());
var fooService = new FooService(ServiceLocator.Resolve<ILogger>());
fooService.Execute();
```

サービスロケーターは便利ですが、使用しすぎるとコードの可読性やテストのしやすさが低下する可能性があります。そのため、適切な場面での使用が求められます。

### DIコンテナ

DIコンテナは、依存性の注入を自動化するためのフレームワークです。C#では、`Microsoft.Extensions.DependencyInjection`などのライブラリが利用できます。

DIコンテナを使用することで、以下の利点があります：

- **依存関係の自動解決**：コンストラクタの引数に応じて、必要な依存性を自動的に注入します。
- **ライフサイクルの管理**：シングルトンやスコープなど、オブジェクトのライフサイクルを管理できます。
- **モジュール化**：依存性の登録をモジュール単位で管理できます。

### 依存性の注入のタイミング

依存性の注入は、主に以下の2つのタイミングで行われます：

- **アプリケーションの起動時**：DIコンテナにサービスを登録し、必要な依存性を解決する際に注入されます。これは一般的にもっとも多いパターンで、ASP.NET Core などのフレームワークでは、`Program.cs`や`Startup.cs`などで設定されます。
- **オブジェクトの生成時**：手動で依存性を注入する場合、対象のオブジェクトを生成するタイミングで注入されます。この方法は、小規模なアプリケーションやテストコードなどで使用されます。

### 依存性注入のライフサイクル
C#での依存性注入（DI）では、3つの依存性注入メソッドのライフサイクル（AddTransient、AddScoped、AddSingleton）が使用できます。



ざっくりまとめると以下のようになっています。

ライフタイム	インスタンスの生成タイミング
- **AddTransient**	要求ごとに新しいインスタンスを生成
- **AddScoped**	リクエストごとに1つのインスタンスを生成
- **AddSingleton**	アプリケーション全体で1つのインスタンスを生成
それぞれ依存性注入するのは変わりませんが、オブジェクトの生成タイミングが違うので実際に比較します。

適切なタイミングで依存性を注入することで、アプリケーションの初期化処理を明確にし、
不要な依存を回避する設計が可能になります。