LoginSignup
1
4

依存性の注入と C# 11

Last updated at Posted at 2023-01-24

依存性の注入と C# 11

C# 11 で static abstract なメンバーをインターフェイスに追加できるようになるのはすごく良い。ずっと欲しかったヤツ。

たとえば MyContainer というクラスがあったとして、登録される側のクラスに static void Register() を強制する手段が今まではなかった。(Obsolete で無理矢理下線引いたりしてた)

--

※ この記事内で扱っている「依存性の注入」は、Unity 向け DI コンテナーにしか存在しない [Inject] の利用を前提にしています。

👉 Unityでの依存性の注入は世間のそれとは意味が違う

いまいち恩恵を感じないまま色々書いてみたけど、もしかしたらアトリビュート付けるだけ系の DI がダメなのかもしれない。Autofac は何もつけずに動みたいだけど、見てて抵抗がない。グローバル変数って感じ。[Inject] じゃなくて [global] だったらすんなり DI 受け入れられるのかも。

依存性注入

Dependency Injection の良くある感じとしては、

// DIコンテナ
Register<T, T>();
Register<T, T>(Nanchara).AsNice();
Register()
    .AsSingleton<T>()
    .LazyInitialize();
...

// 注入
[Inject] Something m_something;

static abstract + ModuleInitializer

これが、C# 11 の static abstract(と C# 9 の ModuleInitializer の組み合わせ)で、

// 注入  <Something>() 消したい
Something m_something = DI.Inject<Something>();


// 自作コンテナ  名前が DI.Inject なだけでロケーターとかプロバイダー、ファクトリー系か??
static class DI {
    static T Inject<T>() where T : IInjectable, new()
    {
        if (isDebug)
            ...
        else
            ...
    }
    static void Register<T>() where T : IInjectable, new() {}
}


// 登録
internal class Something : IInjectable
{
    [ModuleInitializer]  // これもインターフェイスで保証したいね
    internal static void Register() => DI.Register<Something>();
}


// インターフェイス
internal interface IInjectable {
    abstract internal static void Register();  // C# 11
}


// 👇 は出来ない。残念!
internal class IInjectable<T> where T : class
{
    [ModuleInitializer]  //error
    internal static void Register() => DI.Register<T>();
}

Register 羅列は各クラスに分散。静的抽象インターフェイスのおかげで実装漏れもない。Inject 処理は引き続き集中管理、こまかい挙動は自分で自由に作れるようになる。

いわゆるエントリーポイントも不要になり、Unity 的はどのシーンから入るか問題がなくなる。助かる。

ModuleInitializer に実行順を制御する方法がないのがちょっと不安。Unity なら RuntimeInitializeOnLoadMethod のが実行順の制御ができるから便利か。

テスト時

分散したのは Register 処理だけ。引き続き Inject で注入を集中管理出来てるから、全部テスト用のものに差し替えればおっけ。

メソッドへの注入

DIコンテナならできるメソッドの引数に注入するってのはこのやり方だと出来ない。使いどころも良くわからないけど。DIコンテナへの依存を代償にフィールドを少なくできるだけ??

おわりに

DIって依存関係を一か所に羅列しなきゃいけないことが強制される状態になって、結果的に見通しが良くなるのが利点なんかね。

[Inject] と書くだけじゃダメだけど、その時点でエラーは出ない。エントリー内で忘れずに Register しなきゃいけない。Register 不要になった場合はエントリー内で該当部分を消す。でも [Inject] 付きのフィールドは残る。うーーん。散らかり方が変わっただけで片付いていない感じ。

依存してる部分が減る、注入されてもされなくても見かけ上エラーは出ないしコンパイルは通る、けど実際は注入されないとまともに動かないんだから、ロケーターなのかプロバイダーなのかに依存しても良くないか? 引数ありで取得するオブジェクトの調整ができます、さえなければイイじゃんね。

デカくなった DI クラスを後々小分けにしなきゃいけない時にちょっと手間かかるかもだけど、C# みたいな高級言語はリファクタリング楽だし問題なさそうに思える。

注入部分が数百数千あったとしたら DI の入口の Inject は維持して、そこから種類別の InjectResolver に振り分ける形で小分けにするで良さそうな気はする。場当たり的で適切ではない、とか言われるとつらいが。

--

アトリビュート付けるだけってのが怖いし、実行までエラー出ないし(?)処理の流れを辿りづらい。やってることに対してセットアップのおまじない冗長すぎ。

DI分からん ☕

以上です。お疲れ様でした。

1
4
0

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
1
4