💉依存性注入(DI)って何?(C#)

DI C#

開発現場では、「開発中は画面(コンソール)に出したいけれど、本番環境ではテキストファイルに残したい」ということがよくあります。こんなときは「 依存性の注入」(Dependency Injection 略してDI)が使えます。

実践例:ログ出力機能の切り替え

1. インターフェース(共通ルール)を作る

まず、「ログを出力する機能には、こういうメソッドが必要だよね」という規格を決めます。

C#
// ILogger.cs
public interface ILogger
{
    // メッセージを受け取って記録する、というルールだけ決める
    void Log(string message);
}

2. 具体的な部品を作る

このルールに従って、2種類のロガー(記録係)を作ります。

A. 画面に表示するロガー(開発用)

C#
// ConsoleLogger.cs
using System;

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        // 黒い画面(コンソール)に表示する
        Console.WriteLine($"[画面表示]: {message}");
    }
}

B. ファイルに書き込むロガー(本番用)

C#
// FileLogger.cs
using System;
using System.IO;

public class FileLogger : ILogger
{
    private string _filePath = "app_log.txt"; // 保存するファイル名

    public void Log(string message)
    {
        // テキストファイルに追記する
        // (実際の開発ではtry-catch等が必要ですが、簡略化しています)
        string logText = $"{DateTime.Now}: {message}\n";
        File.AppendAllText(_filePath, logText);
        
        Console.WriteLine("(ファイルに書き込みました)");
    }
}

3. ロガーを使う業務クラス(注文処理)

ここがDIの核心です。
「注文処理」クラスは、「ログを取りたい」と思っていますが、「どうやって記録するか(画面かファイルか)」には関心を持ちません。

C#
// OrderService.cs
public class OrderService
{
    // 具体的なクラス(FileLoggerなど)ではなく、インターフェースを持つ
    private readonly ILogger _logger;

    // 【依存性の注入ポイント】
    // コンストラクタで「ILogger」を受け取るのがミソです。
    // これにより、FileLoggerでもConsoleLoggerでも、どっちでも受け入れられます。
    public OrderService(ILogger logger)
    {
        _logger = logger;
    }

    // 注文を確定するメソッド
    public void PlaceOrder(string productName)
    {
        // 処理の開始をログに残す
        _logger.Log($"{productName} の注文処理を開始します。");

        // ... ここにデータベース保存などの複雑な処理があるとする ...

        Console.WriteLine($" >> {productName} の注文が完了しました。");

        // 処理の終了をログに残す
        _logger.Log($"{productName} の注文処理が正常終了しました。");
    }
}

4. 実際に動かしてみる(Mainメソッド)

プログラムの起動部分(Main)で、どの部品を使うか組み立てます。

C#
// Program.cs
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("== パターン1:開発中(画面に出したい) ==");
        
        // 1. 画面用の部品を作る
        ILogger devLogger = new ConsoleLogger();
        
        // 2. 注入して注文サービスを作る
        OrderService serviceDev = new OrderService(devLogger);
        
        // 3. 実行
        serviceDev.PlaceOrder("ゲーミングPC");


        Console.WriteLine("\n--------------------------------------\n");


        Console.WriteLine("== パターン2:本番運用(ファイルに残したい) ==");
        
        // 1. ファイル用の部品を作る
        ILogger productionLogger = new FileLogger();
        
        // 2. 注入して注文サービスを作る(OrderServiceのコードは一切変更なし!)
        OrderService serviceProd = new OrderService(productionLogger);
        
        // 3. 実行
        serviceProd.PlaceOrder("4Kモニター");
    }
}

解説:ここが「実用的」なポイント

この設計の素晴らしい点は、OrderService(注文処理のロジック)を一切書き換えずに、ログの保存先を変更できたことです。

もしDIを使わず、OrderServiceの中で new FileLogger() と書いてしまっていたら……

  1. 「テストしたいから、ファイル書き込みじゃなくて画面に出して」と言われたとき、OrderServiceのコードを書き換える必要があります。
  2. 書き換えるときに、うっかり注文処理のロジックを壊してしまうリスクがあります(バグの温床)。

DIを使えば、部品(ロガー)を外からパカッと入れ替えるだけなので、メインの処理を安全に守ったまま、周辺機能だけを柔軟に変更できるのです。