ASP.NET Coreにおける依存注入(5):ServicePrvider実現の秘密【漏れた細部を補う】

24119 ワード

これまで定義したServiceProviderでは、基本的なサービス提供とリサイクル機能を実現してきましたが、必要な詳細な特性が漏れています.これらのプロパティには、IServiceProviderインタフェースに対してServiceProviderオブジェクトを提供する方法、ServiceScopeを作成する方法、およびサービスインスタンスのセットを提供する方法が含まれます.

一、サービスプロバイダオブジェクトの提供


サービスタイプをIServiceProviderインタフェースとして指定し、サービスプロバイダを呼び出すGetServiceメソッドは、サービスインスタンスとしてサービスプロバイダオブジェクト自体が返されることを知っています.このプロパティは、カスタムサービスを使用して実現できます.次のコード・クリップに示すように、このServiceProviderServiceはサービスであり、サービスCallSiteでもあります.デフォルトでは、現在のServiceProviderは、InvokeメソッドとBuildメソッドで提供されているサービスインスタンスとして直接使用されます.ServiceTableを初期化するときに、ServiceProviderService用のServiceEntryを追加します.
   1: internal class ServiceProviderService : IService, IServiceCallSite
   2: {
   3:     public ServiceLifetime Lifetime => ServiceLifetime.Scoped;
   4:     public IService Next { get; set; }
   5:  
   6:     public Expression Build(Expression provider)
   7:     {
   8:         return provider;
   9:     }
  10:  
  11:     public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
  12:     {
  13:         return this;
  14:     }
  15:  
  16:     public object Invoke(ServiceProvider provider)
  17:     {
  18:         return provider;
  19:     }
  20: }
  21:  
  22: internal class ServiceTable
  23: {
  24:     public ServiceTable(IServiceCollection services)
  25:     {
  26:         //  ServiceCollection     ServiceEntry
  27:         this.ServieEntries[typeof(IServiceProvider)] = new ServiceEntry(new ServiceProviderService());
  28:     }
  29: }

二、ServiceScopeの作成


ServiceScopeを作成する目的は、現在のServiceProviderの息子である別のServiceProviderを作成することです.新しく作成したServiceProviderは、元のServiceProviderと同じルートを持つだけでなく、すべてのサービス登録情報を共有します.この新しいServiceProviderを使用して、既存のServiceProviderの代わりに使用します.主な目的は、提供されているサービスインスタンスをタイムリーに回収できることです.ServiceScopeは、工場のServiceScopeFactoryによって作成されるため、前のセクションで説明した定義と完全に一致する次のServiceScopeFactoryクラスと対応するServiceScopeが作成されます.
   1: internal class ServiceScope : IServiceScope
   2: {
   3:     public IServiceProvider ServiceProvider { get; private set; }
   4:  
   5:     public ServiceScope(ServiceProvider serviceProvider)
   6:     {
   7:         this.ServiceProvider = serviceProvider;
   8:     }
   9:  
  10:     public void Dispose()
  11:     {
  12:         (this.ServiceProvider as IDisposable)?.Dispose();
  13:     }
  14: }
  15:  
  16: internal class ServiceScopeFactory : IServiceScopeFactory
  17: {
  18:     public ServiceProvider ServiceProvider { get; private set; }
  19:  
  20:     public ServiceScopeFactory(ServiceProvider serviceProvider)
  21:     {
  22:         this.ServiceProvider = serviceProvider;
  23:     }
  24:  
  25:     public IServiceScope CreateScope()
  26:     {
  27:         return new ServiceScope(this.ServiceProvider);
  28:     }
  29: }
  30:  
  31: internal class ServiceProvider : IServiceProvider, IDisposable
  32: {
  33:     
  34:     public ServiceProvider(ServiceProvider parent)
  35:     {
  36:         this.Root = parent.Root;
  37:         this.ServiceTable = parent.ServiceTable;
  38:     }
  39: }

ServiceProviderのGetServiceメソッドが、サービス・タイプがIServiceScopeFactoryインタフェースとして指定されたときに、上記で定義したServiceScopeFactoryオブジェクトを自動的に返すようにするために、上記と同様にカスタム・サービスを作成し、ServiceScopeFactoryServiceと名前を付けました.ServiceProviderServiceと同様に、ServiceScopeFactoryServiceはServiceCallSiteでもあり、BuildメソッドとInvokeメソッドではServiceScopeFactoryオブジェクトが返されます.これを有効にするために、ServiceTableの初期化時に対応するServiceEntryを自動的に追加します.
   1: internal class ServiceScopeFactoryService : IService, IServiceCallSite
   2: {
   3:     public ServiceLifetime Lifetime=> ServiceLifetime.Scoped;
   4:     public IService Next { get; set; }
   5:  
   6:     public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
   7:     {
   8:         return this;
   9:     }
  10:  
  11:     public Expression Build(Expression provider)
  12:     {
  13:         return Expression.New(typeof(ServiceScopeFactory).GetConstructors().Single(), provider);
  14:     }
  15:  
  16:     public object Invoke(ServiceProvider provider)
  17:     {
  18:         return new ServiceScopeFactory(provider);
  19:     }
  20: }
  21:  
  22: internal class ServiceTable
  23: {
  24:     public ServiceTable(IServiceCollection services)
  25:     {
  26:         //  ServiceCollection     ServiceEntry
  27:         this.ServieEntries[typeof(IServiceProvider)] =  new ServiceEntry(new ServiceProviderService());
  28:         this.ServieEntries[typeof(IServiceScopeFactory)] = new ServiceEntry(new ServiceScopeFactoryService());
  29:     }
  30: }

三、一組のサービスを提供する集合


従来、カスタムServiceProviderは、GetServiceメソッドを呼び出すときにサービスタイプをIEnumerableに指定したり、拡張メソッドGetServicesを直接呼び出すときにサービスインスタンスのセットを得たりするオリジナルのServiceProviderの特性を備えていません.このプロパティは、EnumerableCallSiteという名前のカスタムServiceCallSiteで実行できます.
   1: internal class EnumerableCallSite : IServiceCallSite
   2: {
   3:     public Type ElementType { get; private set; }
   4:     public IServiceCallSite[] ServiceCallSites { get; private set; }
   5:  
   6:     public EnumerableCallSite(Type elementType, IServiceCallSite[] serviceCallSites)
   7:     {
   8:         this.ElementType = elementType;
   9:         this.ServiceCallSites = serviceCallSites;
  10:     }
  11:  
  12:     public Expression Build(Expression provider)
  13:     {
  14:         return Expression.NewArrayInit(this.ElementType, this.ServiceCallSites.Select(
  15:             it => Expression.Convert(it.Build(provider), this.ElementType)));
  16:     }
  17:  
  18:     public object Invoke(ServiceProvider provider)
  19:     {
  20:         var array = Array.CreateInstance(this.ElementType, this.ServiceCallSites.Length);
  21:         for (var index = 0; index < this.ServiceCallSites.Length; index++)
  22:         {
  23:             array.SetValue(this.ServiceCallSites[index].Invoke(provider), index);
  24:         }
  25:         return array;
  26:     }
  27: }

前述のコードフラグメントに示すように、EnumerableCallSiteには2つの読み取り専用属性(ElementTypeとServiceCallSites)があり、前者は返されるサービスセットの要素タイプを表し、後者はセット要素を提供するためのServiceCallSiteのセットを返す.InvokeメソッドとBuildメソッドでは、要素タイプに基づいて配列を作成し、このServiceCallSiteセットを使用してすべての要素を作成するだけです.このEnumerableCallSiteは最終的にサービスプロバイダのGetServiceCallSiteメソッドに次のように適用される.
   1: internal class ServiceProvider : IServiceProvider, IDisposable
   2: { 
   3:     public IServiceCallSite GetServiceCallSite(Type serviceType, ISet<Type> callSiteChain)
   4:     {
   5:         try
   6:         {
   7:             if (callSiteChain.Contains(serviceType))
   8:             {
   9:                 throw new InvalidOperationException(string.Format("A circular dependency was detected for the service of type '{0}'",serviceType.FullName);
  10:             }
  11:             callSiteChain.Add(serviceType);
  12:             ServiceEntry serviceEntry;
  13:             if (this.ServiceTable.ServieEntries.TryGetValue(serviceType, out serviceEntry))
  14:             {
  15:                 return serviceEntry.Last.CreateCallSite(this, callSiteChain);
  16:             }
  17:  
  18:             if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition()== typeof(IEnumerable<>))
  19:             {
  20:                 Type elementType = serviceType.GetGenericArguments()[0];
  21:                 IServiceCallSite[] serviceCallSites = this.ServiceTable.ServieEntries.TryGetValue(elementType, out serviceEntry)
  22:                     ? serviceEntry.All.Select(it => it.CreateCallSite(this, callSiteChain)).ToArray()
  23:                     : new IServiceCallSite[0];
  24:                 return new EnumerableCallSite(elementType, serviceCallSites);
  25:             }
  26:  
  27:             return null;
  28:         }
  29:         finally
  30:         {
  31:             callSiteChain.Remove(serviceType);
  32:         }
  33:     }
  34:     //    
  35: }

 
ASP.NET Coreにおける依存注入(1):制御反転(IoC)ASP.NET Coreにおける依存注入(2):依存注入(DI)ASP.NET Coreにおける依存注入(3):サービス登録と抽出ASP.NET Coreでの依存注入(4):コンストラクション関数の選択とライフサイクル管理ASP.NET Coreにおける依存注入(5):ServicePrvider実現秘密【全体設計】ASP.NET Coreにおける依存注入(5):ServicePrvider実現秘密【ServiceCallSiteの解読】ASP.NET Coreにおける依存注入(5):ServicePrvider実現の秘密【漏れた細部を補う】