第28章 アンチパターン②:循環参照をほどくコツ🌀
この章のゴール🎯✨
- 「循環参照って何が困るの?」を自分の言葉で説明できる😊
- 例外メッセージを見て「これ循環だ!」って気づける👀💥
- ほどき方(直し方)を3〜5パターン持って、落ち着いて直せる🛠️🌸
1) 循環参照ってなに?🤔🌀

**循環参照(circular dependency)**は、依存関係がぐるっと輪っかになる状態だよ〜😵💫
- 直球の例:
A → B → A - ちょい遠回り:
A → B → C → A
DIコンテナは「必要な部品を順番に作って組み立てる」んだけど、輪っかになると 「A作るにはB必要、B作るにはA必要…え、どっち先!?😇」って詰むの💥
実際、.NETのDIでは循環があると例外で止まることが多くて、メッセージに “A circular dependency was detected…” みたいに出るよ🧯🚨 (GitHub)
2) ありがちな“事故パターン”あるある🥺🧨
初心者さんがハマりやすいの、だいたいこの辺👇
あるある①:相互に「手伝って〜」してる🤝🌀
- ユーザー作成サービスが通知サービスを呼ぶ
- 通知サービスが「メールアドレス欲しい」ってユーザーサービスを呼ぶ → はい輪っか〜🌀
あるある②:責務が混ざって巨大化🍱💦
「便利だからここに全部入れよ〜」で、結果
- AがBの仕事も持つ
- BもAの仕事に手を出す → いつの間にか輪っか🌀
あるある③:層の向きが逆走🚗💨↩️
たとえば「ドメイン(中心)」が「インフラ(外側)」を呼び、 インフラがまた中心を呼ぶ…みたいなやつ😵
3) まずは“見える化”しよう👀🗺️(ここ超大事!)
循環って、直す前にどこが輪っかなのかを見つけるのが9割だよ💪✨
見える化の手順(おすすめ)🧩
-
例外メッセージから「登場人物(型名)」を抜き出す✍️
-
「Aはコンストラクタで何を受け取ってる?」を辿る🔍
-
紙でもメモでもいいから矢印で描く📝
A → B(AのコンストラクタにBがいる)B → A(BのコンストラクタにAがいる)
-
輪っかが見えたら勝ち🏆✨
💡DI関連の公式ドキュメントも「サービスはIServiceCollectionに登録して、必要な依存を解決して組み立てる」流れを前提に説明してるよ📚 (Microsoft Learn)
4) 実例:わざと循環させてみる😈🌀(→直す)
❌ ダメな例(A→B→A)🙈
「ユーザー作る」→「ようこそメール送る」って流れは自然なんだけど…設計によっては輪っかになるよ💦
public interface IUserService
{
Task RegisterAsync(string email);
Task<string> GetEmailAsync(int userId);
}
public interface INotificationService
{
Task SendWelcomeAsync(int userId);
}
public class UserService : IUserService
{
private readonly INotificationService _notification;
public UserService(INotificationService notification)
=> _notification = notification;
public async Task RegisterAsync(string email)
{
int userId = 123; // 例:保存した想定
await _notification.SendWelcomeAsync(userId);
}
public Task<string> GetEmailAsync(int userId)
=> Task.FromResult("test@example.com");
}
public class NotificationService : INotificationService
{
private readonly IUserService _users;
public NotificationService(IUserService users)
=> _users = users;
public async Task SendWelcomeAsync(int userId)
{
var email = await _users.GetEmailAsync(userId);
Console.WriteLine($"Welcome mail to {email}");
}
}
UserServiceはINotificationServiceに依存NotificationServiceはIUserServiceに依存 → はい循環🌀
この状態でDIが解決しようとすると、循環検出の例外が出がちだよ🚨 (GitHub)
5) ほどき方🧶✨(超よく使う5パターン)
「魔法の1手」じゃなくて、状況で使い分ける感じだよ〜😊🌷
パターン①:責務を分けて“共通の部品”を作る✂️🧩(最優先!)
上の例だと、通知側が欲しいのは「メールアドレス」だけだよね?📮
だったら IUserService まるごとじゃなくて、必要最小限の読み取り用を切り出す✨
✅ 例:メール取得専用の小さい口を作る
public interface IUserEmailQuery
{
Task<string> GetEmailAsync(int userId);
}
public class UserEmailQuery : IUserEmailQuery
{
public Task<string> GetEmailAsync(int userId)
=> Task.FromResult("test@example.com");
}
public class NotificationService : INotificationService
{
private readonly IUserEmailQuery _emailQuery;
public NotificationService(IUserEmailQuery emailQuery)
=> _emailQuery = emailQuery;
public async Task SendWelcomeAsync(int userId)
{
var email = await _emailQuery.GetEmailAsync(userId);
Console.WriteLine($"Welcome mail to {email}");
}
}
こうすると
UserService → NotificationServiceは残るNotificationService → UserServiceが消える🎉 → 輪っか解消🧹✨
パターン②:向きを揃える(層のルールを守る)➡️🏗️
ざっくりルールはこれ👇
- **内側(中心)は外側(詳細)**を知らない
- 外側が内側に合わせる(アダプタで包む)🧷
もし「中心のサービス」が「外側の都合」で外側を呼び返してるなら、たぶん設計の向きが逆走してる可能性高いよ🚗💦
パターン③:イベントで“直接呼び出し”をやめる📣✨(ゆるい非同期思考)
「ユーザー登録したら、ようこそメール送ってね」って “お知らせ” にすると循環が消えやすいよ😊
イメージ:
- UserService:「登録したよ〜📣」
- それを聞いた別クラスがメール送る📨
「呼び出す」じゃなくて「通知する」に変える感じ✨ (ここ、設計の世界ではよく効く🧠🌟)
パターン④:Mediator(仲介役)を置く🤝🧑⚖️

AとBが直接話すから揉めるなら、**間に通訳(仲介役)**を置く作戦だよ🧑⚖️✨ 「Aは仲介役に頼む」「Bも仲介役に頼む」→ AとBが直接依存しない👍
(言語は違うけど、循環をMediatorでほどく考え方の例としてはこういう記事があるよ🧠) (DEV Community)
パターン⑤:遅延生成(Lazy / Factory)で“作る瞬間”だけ外す⏳🧰(最終手段寄り)
「設計的には循環じゃないのに、生成タイミングだけが原因」みたいな時は 遅延して必要になった瞬間に作ると回避できることがあるよ⚠️
ただしこれ、使いどころを間違えると 「根本原因を放置してるだけ」になりがち😇
.NETのガイドラインでも、必要ならFactoryパターンや ActivatorUtilities.CreateInstance を使う方向が紹介されてるよ📚🧰 (Microsoft Learn)
(遅延解決の考え方の解説として、こういう記事もあるよ) (Thomas Levesque's .NET Blog)
6) やっちゃダメ寄りの“その場しのぎ”😵💫🚫
🚫 IServiceProvider を握って GetService しまくる
「とりあえず IServiceProvider 注入して、必要な時に GetService すればいいじゃん!」
…ってやると、依存が見えなくなって地獄化しやすいよ🕳️💦
(前章の Service Locator っぽくなるやつ!)
※完全否定じゃないけど、基本は避けたいスタイルだよ🙅♀️
7) ミニ演習コーナー🧪✨(手を動かすと最強)
演習1:矢印を書いて循環を見つけよう🗺️🖊️
-
自分のコード(orサンプル)で
- 「このクラスのコンストラクタ引数」を全部書く
A → Bを全部書く
-
輪っかがあるか探す🌀👀
演習2:パターン①で直してみよう✂️🧩
- Notification側が欲しい情報だけを
IUserEmailQueryみたいに切り出す NotificationServiceがIUserServiceを参照しない形にする
演習3:イベント案を文章で設計してみよう📣📝
-
「ユーザー登録」→「メール送信」を
- 直接呼ぶのではなく
- “登録完了イベント”として通知する って文章で設計案を書いてみてね😊
8) AI(Copilot/Codex)活用テンプレ🤖✨
そのまま貼って使えるやつ用意したよ〜🌸
-
循環の特定
- 「この例外(A circular dependency…)が出ました。依存グラフを矢印で列挙して、循環ループを特定して」
-
責務分割の提案
- 「AとBが相互依存しています。
必要最小限のインターフェース抽出でほどく案を3つ出して」
- 「AとBが相互依存しています。
-
安全チェック
- 「提案された修正が Service Locator 化していないか、依存が“引数で見える”か観点でレビューして」
章末まとめ🍰✨
- 循環参照は「どっちが先に作られるの問題」でDIが詰む🌀
- 直し方の基本は 責務分割+小さいインターフェース✂️🧩
- イベント/仲介役(Mediator)/Factoryは状況に応じて✨
- その場しのぎで
IServiceProvider.GetService()連打は危険⚠️
次の章(第29章)は、今回の“根っこ”にもなりやすい **「巨大コンストラクタはSOSサイン📣」**を扱うよ〜!つながってて気持ちいいはず😊💖