30
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# / .NETで始める依存性注入(DI)

30
Posted at

C# や .NET を触っていると、DI(依存性注入)という言葉をどこかで必ず耳にします。
ただ、実際のところはどうでしょう。

  • コンストラクタでインターフェースを受け取っているけど、理由はよく分からない
  • DI コンテナって便利らしいけど、何が嬉しいのかピンとこない
  • ASP.NET Core では勝手に動いてるけど、仕組みを理解してるかと言われると微妙

こんな感覚を持っている人は意外と多いんです。

でも DI は、特定のフレームワークの機能ではありません。
もっと根っこの部分にある、“設計を柔らかくするための考え方” です。
コンソールアプリでも、ライブラリでも、WPF でも、Unity でも、.NET であればどこでも活かせます。

この記事では、
「DI を使うと何が変わるのか」
「どう書けば実務で役立つのか」
を、できるだけ肩の力を抜いて話していきます。


1. DI が解決したい問題は「結びつきが強すぎる」こと

まずは DI が何を解決するのかを押さえておきましょう。

例えば、こんなコード。

public class UserService
{
    private readonly FileLogger _logger = new FileLogger();

    public void CreateUser(string name)
    {
        _logger.Log($"User created: {name}");
    }
}

一見シンプルですが、問題があります。

  • UserServiceFileLogger にガッチリ依存している
  • ログの出力先を変えたいときに UserService を修正しないといけない
  • テストで差し替えができない

つまり、変更に弱い設計になってしまうんです。


■ Before:依存を自分で抱え込む(密結合)


2. DI の本質は「依存を外から渡す」だけ

DI と聞くと難しく感じますが、やっていることはとてもシンプルです。

必要なものを自分で new せず、外から渡してもらう

これだけです。

先ほどの例を DI を使って書き直すとこうなります。

public class UserService
{
    private readonly ILogger _logger;

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

    public void CreateUser(string name)
    {
        _logger.Log($"User created: {name}");
    }
}

こうすることで、

  • ログの実装を差し替えられる
  • テストでモックを渡せる
  • UserService が余計な責務を持たなくなる

といったメリットが生まれます。


■ After:依存を外から渡す(疎結合)


3. .NET で DI を使う方法は 2 種類ある

① 自前で注入する(手動 DI)

コンソールアプリや小規模なツールでは、これで十分です。

ILogger logger = new FileLogger();
var service = new UserService(logger);
service.CreateUser("Taro");

手動 DI のメリットは「仕組みがシンプルで理解しやすい」こと。
DI の本質を理解するには、まずここから始めるのが一番です。


② DI コンテナを使う(自動 DI)

規模が大きくなると、依存関係を手でつなぐのが大変になります。
そこで登場するのが DI コンテナです。

.NET には標準 DI コンテナがあり簡単に使えます。

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddSingleton<ILogger, FileLogger>();
services.AddTransient<UserService>();

var provider = services.BuildServiceProvider();

var service = provider.GetRequiredService<UserService>();
service.CreateUser("Taro");

■ DI コンテナによる自動解決の流れ

  • 登録:どのインターフェースにどの実装を使うかを DI コンテナに教える
  • 依存解決:必要になったときに、依存関係をたどって自動で生成
  • 完成:依存が注入された状態のインスタンスが手に入る

4. ライフサイクル(Singleton / Scoped / Transient)の理解は必須

DI コンテナを使うときに避けて通れないのがライフサイクルです。

  • Singleton
    アプリ終了まで同じインスタンス
    → 設定、キャッシュ、軽量なサービス向け

  • Scoped
    “スコープ”ごとにインスタンスを共有
    → Web では「1リクエスト = 1スコープ」
    → コンソールアプリでは自分でスコープを作る

  • Transient
    注入されるたびに新しいインスタンス
    → ステートレスな処理向け


5. インターフェースを使う理由は「差し替えやすさ」

DI とセットで語られるのが「インターフェースを使いましょう」という話。
理由はシンプルです。

  • 実装を差し替えられる
  • テストでモックを使える
  • クラスの責務が明確になる

ただし、実装が1つしかないのに無理にインターフェースを作る必要はありません
実務では「差し替えの可能性があるか」で判断するのが自然です。


6. DI を使わないとどう破綻するか

DI の価値が一番わかるのは、使わなかった場合の破綻を知ったときです。


■ ある日、ログ出力先を変えることになった

あなたのプロジェクトでは、ログをファイルに書き出す FileLogger を使っていました。

public class UserService
{
    private readonly FileLogger _logger = new FileLogger();
}

ところがある日、上司からこう言われます。

「ログをクラウドに送るようにしてほしい」

あなたは青ざめます。

なぜなら…

  • FileLogger直接 new しているクラスが大量にある
  • それらを全部探して書き換える必要がある
  • テストも全部壊れる
  • 変更の影響範囲が読めない

密結合のツケが一気に爆発します。


■ DI を使っていた場合

もし DI を使っていたら、変更はたった 1 行

// 変更前
services.AddSingleton<ILogger, FileLogger>();

// 変更後
services.AddSingleton<ILogger, CloudLogger>();
  • 既存のサービスは ILogger しか知らない
  • 実装の差し替えは DI コンテナに任せるだけ
  • テストも壊れない
  • 影響範囲は最小限

DI の価値はここにあります。


7. 実務でよくある DI の落とし穴

● コンストラクタが太りすぎる

依存が多すぎるクラスは、そもそも責務が多すぎるサインです。

● なんでも Singleton にする

状態を持つクラスを Singleton にすると、予期せぬバグの温床になります。

● DI コンテナに登録しすぎる

「とりあえず全部登録」は逆効果。
本当に必要なものだけ登録するのが鉄則です。


8. まとめ:DI は「設計を柔らかくするための道具」

DI はフレームワークの機能ではなく、設計の考え方です。

  • 依存を外から渡す
  • 実装を差し替えられる
  • テストしやすくなる
  • クラスの責務が整理される

.NET では標準 DI コンテナがあるので、どんなアプリでも DI を取り入れられます。
まずは「自分で new しない」ことを意識してみると、DI の良さが自然と見えてきます。


30
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?