ASP.NET Core DIのライフタイムの種類
ASP.NET CoreのDIで設定できるライフタイムについてのメモです。
DIについては特に説明しません。
インスタンスのライフタイム
公式ドキュメントにはService lifetimes
と書かれていますが、注入するインスタンスのライフタイムは以下の3つから選択することができます。
- Transient
- Scoped
- Singleton
Transient
Transientは指定したクラスのインスタンスが要求される度に毎回新しく生成します。
インスタンスはリクエストの終了時に破棄されます。
AddTransient
拡張メソッドで設定します。
Scoped
Scopedはリクエストごとにインスタンスを生成します。
Transientと違い、1リクエスト内では同じオブジェクトを使い回すようです。
Entity FrameworkのDbContextはデフォルトでscopedとなっているみたいですね。
インスタンスが破棄されるタイミングはTransientと同じです。
AddScoped
拡張メソッドで設定します。
Singleton
Singletonは最初のリクエスト時にインスタンスを生成し、それ以降はそのインスタンスをずっと使いまわします。
アプリが終了するまでインスタンスは破棄されないまま残り続けます。
AddSingleton
拡張メソッドで設定します。
実際に確認してみる
それぞれのライフタイム設定の違いを確認してみます。
確認用に以下のコードを用意しました。
public interface IMyDependency { Guid Guid { get; } } public class MyDependency : IMyDependency { private readonly Guid _guid; public MyDependency() { _guid = Guid.NewGuid(); } public Guid Guid => _guid; } public class IndexModel : PageModel { private readonly IMyDependency _myDependency; public Guid Guid { get; set; } public IndexModel(IMyDependency myDependency) { _myDependency = myDependency; } public void OnGet() { Guid = _myDependency.Guid; } }
IndexModel
がGuidを持つMyDependency
クラスに依存している状態です。
IndexModel
にMyDependency
が注入されるように設定します。
まずはSingletonから確認します。
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IMyDependency, MyDependency>(); // new services.AddRazorPages(); }
この状態でアプリを起動して、IndexModel
のGuidを表示してみます。
上記のような文字列が出てくると思いますが、何度リロードしても同じ値が表示されているかと思います。アプリを再起動しないと値が変化しません。
ちゃんとSingletonになっていますね。
次はTransientとScopedを比較してみます。
比較しやすいようにコードを修正します。
public interface IMyService { Guid Guid { get; } } public class MyService : IMyService { private readonly IMyDependency _myDependency; public MyService(IMyDependency myDependency) { _myDependency = myDependency; } public Guid Guid => _myDependency.Guid; } public class IndexModel : PageModel { private readonly IMyService _myService1; private readonly IMyService _myService2; public Guid Guid1 { get; set; } public Guid Guid2 { get; set; } public IndexModel(IMyService myService1, IMyService myService2) { _myService1 = myService1; _myService2 = myService2; } public void OnGet() { Guid1 = _myService1.Guid; Guid2 = _myService2.Guid; } }
新たにMyService
というクラスを追加し、依存関係を以下のようにしました。
IndexModel -> IMyService -> IMyDependency
この状態でMyDependency
をTransinetで指定した場合とScopedで指定した場合のGuid1, Guid2の違いを見てみます。
まずはTransinet
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMyDependency, MyDependency>(); // new services.AddTransient<IMyService, MyService>(); // new services.AddRazorPages(); }
IndexModel
のGuid1とGuid2はそれぞれ別の値が表示されると思います。
MyDependency
にTransientを指定しているのでmyService1
とmyService2
が持つMyDependency
は別のインスタンスであると確認できました。
今度はMyDependency
にScopedを指定してみます。
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMyDependency, MyDependency>(); // new services.AddTransient<IMyService, MyService>(); services.AddRazorPages(); }
Guid1と2の表示結果
どちらも同じ値になりました。
Scopedはリクエスト毎にインスタンスを生成し、リクエスト内で使い回すため、
myService1
とmyService2
は同じMyDependency
インスタンスを参照しているってことになりますね。ちゃんとScopedが機能していました!
ざっくりまとめ
- Transient: 毎回新しくインスタンスを生成する。リクエスト終了時に破棄。
- Scoped: 1リクエストで1つインスタンスを生成し、同じリクエスト内で使い回す。リクエスト終了時に破棄。
- Singleton: 最初のリクエストでインスタンスを生成し、それ以降ずっと同じものを使い回す。アプリ終了まで破棄されない。
ドキュメントだけではTransientとScopedの違いがよくわかりませんでしたが、実際に動作確認することで違いが理解できました。