コーヒー飲みながら仕事したい

仕事で使う技術的なことの備忘録とか


Wordpress に引っ越しました!

Microsoft.Extensions.DependencyInjection の DI コンテナの挙動について

移転しました。

ASP.NET Core でよく使われる(?) Microsoft.Extensions.DependencyInjection をクラスライブラリやコンソールアプリケーションで使用したときの挙動についての備忘録です。

Microsoft.Extensions.DependencyInjection の DI コンテナの依存性の注入方法

主に以下の3パターンがあります。

  • AddTransient
  • AddScoped
  • AddSingleton

AddSingleton はその名前の通りなのでいいのですが、残り2つがぱっと見わからないので、それを今回確認しました。

わかったこと

stackoverflow.com

stackoverrun.com

どうやら、AddTransientAddScoped はスコープ設定をするかどうかで挙動がかわるみたいです。

実際に以下のように試してみました。

試したこと

まず、適当にDTOを用意します。

interface ISingleton
{
    string Hoge { get; set; }
}

class Singleton : ISingleton
{
    public Singleton()
    {
        Hoge = Guid.NewGuid().ToString();
    }

    public string Hoge { get; set; }
}

interface IScoped
{
    string Piyo { get; set; }
}

class Scoped : IScoped
{
    public Scoped()
    {
        Piyo = Guid.NewGuid().ToString();
    }

    public string Piyo { get; set; }
}

interface ITransient
{
    string Fuga { get; set; }
}

class Transient : ITransient
{
    public Transient()
    {
        Fuga = Guid.NewGuid().ToString();
    }

    public string Fuga { get; set; }
}

テスト用のソースとして以下を用意しました。

public void GetServiceTest()
{
    var services = new ServiceCollection();

    // 依存性の注入
    services.AddSingleton<ISingleton, Singleton>();
    services.AddScoped<IScoped, Scoped>();
    services.AddTransient<ITransient, Transient>();

    // DIコンテナの生成
    var serviceProvider = services.BuildServiceProvider();

    Console.WriteLine("スコープなし1回目: Singleton:" + serviceProvider.GetRequiredService<ISingleton>().Hoge);
    Console.WriteLine("スコープなし1回目: Scoped:" + serviceProvider.GetRequiredService<IScoped>().Piyo);
    Console.WriteLine("スコープなし1回目: Transient:" + serviceProvider.GetRequiredService<ITransient>().Fuga);

    Console.WriteLine("スコープなし2回目: Singleton:" + serviceProvider.GetRequiredService<ISingleton>().Hoge);
    Console.WriteLine("スコープなし2回目: Scoped:" + serviceProvider.GetRequiredService<IScoped>().Piyo);
    Console.WriteLine("スコープなし2回目: Transient:" + serviceProvider.GetRequiredService<ITransient>().Fuga);

    using (var scope = serviceProvider.CreateScope())
    {
        Console.WriteLine("スコープあり: Singleton:" + scope.ServiceProvider.GetRequiredService<ISingleton>().Hoge);
        Console.WriteLine("スコープあり: Scoped:" + scope.ServiceProvider.GetRequiredService<IScoped>().Piyo);
        Console.WriteLine("スコープあり: Transient:" + scope.ServiceProvider.GetRequiredService<ITransient>().Fuga);
    }
}

これを実行した結果です。

スコープなし1回目: Singleton:3a3fdfd6-50d7-4c9b-9dcf-9a316a554b06
スコープなし1回目: Scoped:f6c3fe39-b608-4083-aff5-98dc6b66b8de
スコープなし1回目: Transient:b209bf1d-c83f-434a-8957-9bc528e9405c
スコープなし2回目: Singleton:3a3fdfd6-50d7-4c9b-9dcf-9a316a554b06
スコープなし2回目: Scoped:f6c3fe39-b608-4083-aff5-98dc6b66b8de
スコープなし2回目: Transient:84d37074-17dc-4f93-845b-4e7125361955
スコープあり: Singleton:3a3fdfd6-50d7-4c9b-9dcf-9a316a554b06
スコープあり: Scoped:990c5065-586c-4e11-b8e7-094a59c1b511
スコープあり: Transient:e10ebd8c-2f23-42c2-ba0d-7d1b2e2cb7f5

まとめ

時間がないのできなりまとめですが、以下の通りです。

ASP.NET Core では、デフォルトで以下のように動作します。

依存性の注入方法 GetSerivece()を実行したときの振る舞い
AddTransient 同一リクエストであっても、常に新しいインスタンスが生成される
AddScoped 同一のリクエストであれば、同じインスタンスが返ってくる。ただし、異なるリクエストならば新しいインスタンスが生成される
AddSingleton 異なるリクエストであっても、常に同じンスタンスが返ってくる

コンソールアプリやクラスライブラリでは以下のように動作します。

依存性の注入方法 GetSerivece()を実行したときの振る舞い
AddTransient 常に新しいインスタンスが生成される
AddScoped スコープを自身で設定する必要がある。何もスコープを設定しないときは、グローバルスコープとなり常に同じインスタンスが返ってくる
AddSingleton 常に同じンスタンスが返ってくる

スコープを設定するために IServiceScopeFactory なるものがあるらしく、自前でそれを使用することでスコープの制御ができるみたいです。

たぶんですけど、ASP.NET Core ではそのスコープ制御を隠蔽して勝手にやってくれているのだと思います。

参考サイト

stackoverflow.com

stackoverrun.com