Skip to content
nishi_74322014 edited this page Sep 1, 2026 · 1 revision

DI

概要

DI

DI: Dependency Injection(依存性の注入)

  • Dependency Injection の Dependency は依存性と訳すと誤解を生む。
  • 正確には「使われるオブジェクト」(依存オブジェクト)という意味。

補足(この指摘は重要): 原文が最初に注意している通り、
「依存性の注入」という訳語が理解を妨げている

Dependency Injection
  =「依存しているもの(=相手のオブジェクト)」を「注入する」

×「依存性という何かを注入する」
○「依存先のオブジェクトを、外から渡してもらう」

つまり、やっていることは
**「new を自分で書かず、引数で受け取る」**という、
それだけのことである。

// DI でない:自分で相手を作る=相手に縛られる
public class OrderService
{
    private readonly SqlOrderRepository _repo = new SqlOrderRepository();
}

// DI:相手を外から渡してもらう=差し替えられる
public class OrderService(IOrderRepository repo) { }

DI とは?

依存性を無くすために、動的に動作を注入する。

依存性

  • あるクラスに、固定の定数、変数、インスタンスが入っている状態
  • つまりそのクラスは、その定数、変数、インスタンスに依存している。

注入

  • そのクラスの外から定数、変数、インスタンスを設定する。
  • これにより、動的に動作を変えられるようにする。

移行メモ(表現): 「依存性を無くすために」とあるが、
依存そのものが無くなるわけではない(相手は必ず要る)。
正確には 「具象への依存を、抽象への依存に置き換える」
依存性反転原則)である。
原文の表現を保ちつつ、この点を注記しておく。

ユースケース

※「依存性がコアの懸念で、AOP のアスペクトとはコアの懸念ではない。」
と言う意見もある。

IoC」, 「AOP」の実現

IoC」, 「AOP」を実現する技術の一つ。

依存性反転原則」の実現

依存性反転原則を実現する技術の一つ。

  • モックの差し替え
    モックの差し替えなどを実現し、テストを容易にする。

  • フレームワークの差し替え
    フレームワークの差し替えを実現し、特定のフレームワークへの依存性を減らす。

テストでの利用

モック差し替え。

フレームワーク自身の利用

Session、認証、ASP.NET Identity の config 等を Program.cs とか、Setup.cs に書かせるアレ。

補足(「書かせるアレ」の正体): 原文が指しているのは、
ASP.NET Core の起動時に書く次のようなコードである。

// Program.cs
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
builder.Services.AddAuthentication(...).AddCookie(...);
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(cs));
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();

ここが **合成ルート(Composition Root)**と呼ばれる場所で、
「何を何に差し替えるか」をアプリ全体で 1 箇所に集めるという設計になる。

従来の web.config による設定と比べると、

web.config Program.cs(DI)
記述 XML(文字列) C#(型チェックが効く)
誤りの検出 実行時 コンパイル時
条件分岐 困難(変換ファイル) 普通に if が書ける
発見可能性 属性名を知る必要 インテリセンス

という違いがある。
詳細は .NET Core における DI /
ASP.NET Core における DI を参照。

詳細

DI の種類

プログラムに依存性を注入する方法としては、以下のような手法が存在する。

interface 注入

注入用の interface を定義して注入を行う方法

setter 注入

setter メソッドを定義して注入を行う方法

constructor 注入

constructor を定義して注入を行う方法

補足(どれを使うべきか): 結論から言うと、
constructor 注入を既定とするのが .NET の定石である。

方式 長所 短所
constructor 注入 必須の依存が明確readonly にできる。未設定で使えない 引数が増えると目立つ
setter(プロパティ)注入 任意の依存に向く 未設定のまま使われ得る(NullReference)
interface 注入 明示的 記述量が多い。現在ほぼ使われない
// constructor 注入(C# 12 のプライマリ コンストラクタ)
public sealed class OrderService(
    IOrderRepository repo,
    ILogger<OrderService> logger)
{
    public void Place(Order o) { logger.LogInformation("..."); repo.Add(o); }
}

「引数が増えて見苦しい」のは、設計の警告灯である
(そのクラスが多くの責務を持ちすぎている、というサイン)。
隠すために setter 注入やサービス ロケータに逃げるのではなく、
クラスを分割するのが正しい対処になる。

なお、ASP.NET Core の標準 DI が既定で対応しているのは
constructor 注入のみである
(プロパティ注入は Autofac 等のサードパーティ コンテナーが必要)。

コンテナー

  • 依存関係を作成する専用のクラス。

    • 要求された依存関係をインスタンスの作成で行う。
      • 依存関係はハードコーディングではなく、構成により宣言。
      • 依存関係のある型のインスタンスを提供するファクトリ。
    • 依存関係だけでなく、ライフサイクルも管理する。
  • 以下のように呼ばれる。

    • 制御の反転 (IoC) コンテナー
    • 依存関係の挿入 (DI) コンテナー

補足(ライフサイクル=最も事故が多い所): 原文が挙げる
「ライフサイクルも管理する」は、実務で最も事故が起きる論点である。

.NET 標準 DI の 3 つのライフタイム。

登録 生存期間 用途
AddSingleton アプリ全体で 1 つ 設定、キャッシュ、HttpClient ファクトリ
AddScoped 1 リクエストにつき 1 つ DbContext、リポジトリ、UoW
AddTransient 要求のたびに新規 軽量・状態を持たないもの

典型的な事故:Captive Dependency(囚われの依存)

builder.Services.AddSingleton<CacheService>();   // ← Singleton が
builder.Services.AddScoped<AppDbContext>();      // ← Scoped を持つと…

// CacheService(AppDbContext db) ← db が最初の 1 つに固定され、
//   ・スレッド安全でない DbContext が並行アクセスされる
//   ・破棄済みの DbContext を使い続ける(ObjectDisposedException)

長生きな側が、短命な側を保持してはいけない

Singleton  →  Singleton        ○
Singleton  →  Scoped           ×  ← Captive Dependency
Scoped     →  Transient        ○
Transient  →  Scoped           △(呼ばれ方次第)

検出方法: ASP.NET Core は開発環境で
スコープの検証を既定で有効にしており、
Singleton から Scoped を解決しようとすると起動時に例外になる。
本番でも有効にしておくと安全である。

builder.Host.UseDefaultServiceProvider(o => {
    o.ValidateScopes = true;
    o.ValidateOnBuild = true;   // 起動時に全登録を検証する
});

Singleton から Scoped を使いたい場合は、
IServiceScopeFactory でスコープを明示的に作る

using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

その他

問題点

複雑さが増す。

IoC」や「AOP」、「依存性反転原則」自体にも言えるが、
同時に全体として協調動作させるときに複雑さが増す。

補足(過剰な DI への警戒): この指摘は現在も有効である。
よくある行き過ぎとして、

  • 実装が 1 つしかないのに、すべてインターフェイスを切る
    IOrderServiceOrderService が 1 対 1 で、差し替える予定も無い)
  • IFoo のためだけに IFooFactory を作る
  • コンテナーの機能(デコレーター、条件付き登録)に依存しすぎて、
    起動時のコードを読んでも実行時の構成が分からない

判断の目安は「差し替える具体的な理由があるか」である。

理由 抽象化する価値
テストで差し替えたい(DB、外部 API、時刻) 高い
実装が複数ある(SQL Server / PostgreSQL) 高い
「将来変わるかもしれない」だけ 低い

近年は、時刻すら TimeProvider(.NET 8 で標準追加)で
注入できるようになった。テスト容易性のための抽象化は、
標準にあるものを使うのが最も安全である。

共通 I/F が無い場合。

  • 共通 I/F が無い場合の DI はユーザのレイヤでしか利用できない。
    (ユーザが DI したものなので、ユーザが型情報を知っている)

  • 下位レイヤで利用するには、≒ 共通 I/F が必要。
    (ユーザが DI したものなので、下位レイヤは型情報を知らない)

移行メモ(表記): 原文の「共通I/Fが無いDI場合のDI」は
重複の入力誤りと判断し、**「共通 I/F が無い場合の DI」**に修正した。

補足(この論点の意味): フレームワーク開発の観点からの指摘で、
要するに 「抽象(インターフェイス)は、下位レイヤ側に置く必要がある」
という話である。

【インターフェイスを上位に置く】        【下位に置く(正しい)】

  上位(業務)                            上位(業務)
    IFoo を定義                             IFoo を使う
    ↑ 参照できない                            ↑
  下位(基盤)                            下位(基盤)
    IFoo を知らない                         IFoo を定義し、実装も提供
      → DI しても使えない                     → 上位からも下位からも使える

これはまさに **依存性反転原則**の
実践であり、Clean Architecture / Onion Architecture で
「インターフェイスは利用する側(内側)に置く」と言われるのと同じ論点である。
フレームワーク側が共通の抽象(契約)を定義して公開する

必要がある、というのが原文の主張と読める。

.NET では、この「共通 I/F」にあたるものが
Microsoft.Extensions.* の抽象パッケージ
ILoggerIConfigurationIDistributedCacheIServiceProvider
として標準化されている。

実現方法

.NET における DI

補足(現在の .NET における DI/最新化): 原文の記述は
ASP.NET Core 登場時点のもので、その後さらに一般化している。

時期 状況
.NET Framework 時代 標準の DI が無い。Unity / Autofac / Spring .NET / Ninject 等
ASP.NET Core 1.0 Microsoft.Extensions.DependencyInjection が標準搭載
.NET Core 3.0 汎用ホストにより、Web 以外(コンソール、Worker)でも標準化
現在 IHostBuilder を使うアプリはすべて DI が前提

つまり **DI は「ASP.NET Core の機能」ではなく「.NET の機能」**になった。

// コンソール アプリでも同じ
var host = Host.CreateApplicationBuilder(args);
host.Services.AddSingleton<IMyService, MyService>();
host.Services.AddHostedService<Worker>();
await host.Build().RunAsync();

標準コンテナーは意図的に機能が絞られている
(constructor 注入のみ、プロパティ注入なし、
インターセプトなし、名前付き登録は限定的)。
より高度な機能が要る場合は Autofac 等に差し替えられるが、
まず標準で足りるかを検討するのが現在の順序である。

なお、Microsoft.Extensions.DependencyInjection
.NET Framework 向けの netstandard2.0 でも使えるため、
既存の .NET Framework アプリに先行導入して
.NET Coreへの移行 の足がかりにする、
という使い方もできる。

参考

Qiita

Microsoft Learn


Tags: 移行, プログラミング, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally