.NET/C#でもヘッダファイルしたいんじゃい！Shadow DIでClean Architectureを影から支えられるかもしれん ～MSBuildと属性で作る“依存しない依存性注入”のススメ～

# 概要

一週間ほど前、

[俺は今日、Shadow Dependency Injection っていう最強概念を発明したぜ──Clean Architecture超えたかもしれん](https://qiita.com/cozyupk/items/0f85ead33ae4158d7495)

という記事を書かせていただきましたが（というかほぼGPTとのChatを転載しただけw)、昨日無事 GitHub に PlayGround レベルのソースコード（.NET/C#) を公開しましたので謹んでご報告申し上げます。

https://github.com/cozyupk/HelloShadowDI

## 利用イメージ

何がしたいのかというと、結局 C/C++ でいうところのヘッダファイル、Java でいう SPI のような、**"アプリケーションのメインコンポーネントが依存するのは、利用するモジュールとの契約まで。ソースコードレベルで実装には依存しない"** という世界を軽量に、C#/.NET の世界で実現したかったんですね。

※ .NET の世界には MEF もありますが、重いっ、ってことで(*'▽')

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/099c6a8b-27f0-40a2-b353-00b2d2519b79.png)

[左: 普通のDIコンテナ利用例（だと思う）](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/ComponentRoots/EntryPointWithoutShadowDI/Program.cs)
[右: 本ShadowDIでのコンテナ利用（RegisterTypeを直接書かない）](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/ComponentRoots/EntryPointWithShadowDI/Program.cs)

※ 「普通のコンテナ利用例」も、依存性逆転したクリーンアーキテクチャのシンプルな例、に、なれてるはず！笑

じゃあどうやってこの IMessageModel とか IMessageWriterDispatcher の実装がコンテナに Inject されるかというと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/77ad3f84-8d08-4c9f-98b8-82d9c7a72c4f.png)

ShadowInjectable という属性をクラスに付与 ([MessageModelの例](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/Models/Impl/MessageModel.cs#L11) / [MessageWriterDispatcherの例](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/Presentation/Impl/MessageWriterDispatcher.cs#L12)) し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/ff0cba33-4f3a-4029-8daf-50bbd4da1d61.png)

.csproj 側で「依存関係生成」のための .target ファイル（※）を Import ([MessageModelの例](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/Models/Impl/02200_Impl.csproj#L13) / [MessageWriterDispatcherの例](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/Presentation/Impl/03200_Impl.csproj#L13)) して、

※ MSBuildの拡張ポイント、昔でいう Makefile の一部

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/773b8e36-4349-4574-9d07-52750db49f61.png)

[エントリポイント側の .csproj で「依存関係取得」のための .target ファイルを Import](https://github.com/cozyupk/HelloShadowDI/blob/af987b2125c71dc4be08083cf2c1529c1fc31a47/ComponentRoots/EntryPointWithShadowDI/09200_EntryPointWithShadowDI.csproj#L13) する形です。

ちなみに各種ファイルの先頭に5桁の番号が付いているのは、ソリューションエクスプローラー上で例の「同心円」を再現しようという趣味です 笑

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/9bade376-3558-4318-9bd5-e17f97f87366.png)

## 利用結果イメージ
### ShadowDI不使用の場合
もちろん、エントリポイント(DIコンテナにInjectをする側)では、「実装」コンポーネントへの依存（プロジェクト参照）が発生します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/12fb1b25-9574-415b-8378-e6cc23b559ea.png)

これはつまり、エントリポイントは実装の public メンバに自由にアクセス可能、ということで、逆に、**実装(Impl)側では、いろんなものを public にする際は警戒を怠れない**ということになります。

### ShadowDI使用の場合
エントリポイントにおいても、「実装」コンポーネントへのプロジェクト参照が発生しません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4041503/5759e2c8-92a4-427c-9a3a-2f8b3067a013.png)

これはつまり、**実装(Impl)側で、エントリポイントや他のコンポーネントから依存されない安心感があるので、未来の拡張実装で必要そうなメンバは public にすることができる(≒OCPにおけるOpenな「拡張ポイント」を作っておける)** ということなんじゃないかとも考えられます。（もちろんコンポーネント間の依存も Contracts 限定の想定、Impl依存は、同レイヤの未来の拡張コンポーネントから、という意味。）

あと大規模プロジェクトとかだとコードメトリクスにも結構良い影響が出てくるんじゃないかと想像してますが、そういったプロジェクトでの試験は未実施です。（ソフトウェア工学の大学生/大学院生の卒論・修論ネタになりそう。誰か書きません？w）

# TODO
 * インタフェイス側のアノテーション
     * このインタフェイスはShadowInjectable実装必須だよ！的な
 * コンテナ登録時のスコープ関連（子コンテナの扱いとかどうする？）
 * 診断ログ
 * 優先度つきの ShadowInjectable 属性とか
 * ドキュメンテーション

みたいなところでしょうか。


時間があるときにでも頑張れるかもしれません。（やらない権利の最大限留保w）


おしまい