ASP.NET Coreにおける依存注入(5):ServiceProvider実現の秘密【ServiceCallSiteの解読】

35413 ワード

前回の紹介では、ServiceProviderでの全体的な設計を実現するための大まかな理解を得る必要がありますが、サービスインスタンスが最終的にどのような方法で提供されるのかという重要な話題を回避するために努力しています.サービスプロバイダが最終的に必要なサービスインスタンスをどのように提供するかは、最終的にどのようなサービスCallSiteを選択したかによって異なり、サービス登録が採用されているサービスDescriptorは、サービスCallSiteタイプの選択を決定します.多くの異なるタイプのServiceCallSiteは、最終的なサービスインスタンスを作成するために使用されるグループと、ライフサイクルの管理に関連するクラスの2つのグループに大きく分けられます.

一、サービス作成のためのServiceCallSite


サービスインスタンスの作成方法は、主に3つあります.ServiceDescriptorには、次の3つの読み取り専用プロパティがあります.簡単に言えば、ImplementationInstanceプロパティが特定のオブジェクトを返す場合、そのオブジェクトは直接サービスインスタンスとして使用されます.プロパティImplementationFactoryが特定の委任オブジェクトを返すと、その委任はサービスインスタンスを提供するファクトリとして機能します.それ以外に、ServiceProviderは、ImplementationTypeプロパティが返す真のサービスタイプを使用して、最適な構造関数を特定し、最終的に提供されるサービスインスタンスを作成します.
   1: public class ServiceDescriptor
   2: {
   3:     public Type                               ImplementationType {  get; }
   4:     public object                             ImplementationInstance {  get; }
   5:     public Func<IServiceProvider, object>     ImplementationFactory {  get; }      
   6: }

サービスインスタンスの3つの異なる作成方法は、最終的には、InstanceCallSite、FactoryCallSite、ConstructorCallSiteの3つの対応するサービスCallSiteタイプによって行われます.次のコードフラグメントに示すように、前の2つのServiceCallSite(InstanceCallSiteとFactoryCallSite)の実装は非常に簡単なので、ここではあまり紹介しません.
   1: internal class InstanceCallSite : IServiceCallSite
   2: {
   3: public object Instance { get; private set; }
   4:  
   5:     public InstanceCallSite(object instance)
   6:     {
   7:         this.Instance = instance;
   8:     }
   9:     public Expression Build(Expression provider)
  10:     {
  11:         return Expression.Constant(this.Instance);
  12:     }
  13:     public object Invoke(ServiceProvider provider)
  14:     {
  15:         return Instance;
  16:     }
  17: }
  18:  
  19: internal class FactoryCallSite : IServiceCallSite
  20: {
  21:     public Func<IServiceProvider, object> Factory { get; private set; }
  22:     public FactoryCallSite(Func<IServiceProvider, object> factory)
  23:     {
  24:         this.Factory = factory;
  25:     }
  26:     public Expression Build(Expression provider)
  27:     {
  28:         Expression<Func<IServiceProvider, object>> factory = p => this.Factory(p);
  29:         return Expression.Invoke(factory, provider);
  30:     }
  31:     public object Invoke(ServiceProvider provider)
  32:     {
  33:         return this.Factory(provider);
  34:     }
  35: }

指定したコンストラクション関数を実行してサービスインスタンスを作成するConstructorCallSiteは、少し複雑です.次のコードクリップに示すように、コンストラクション関数を表すConstructorCallSiteオブジェクトを作成する際には、コンストラクション関数を表すConstructorInfoオブジェクトのほかに、対応するパラメータリストを初期化するためのServiceCallSiteのセットを指定する必要があります.
   1: internal class ConstructorCallSite : IServiceCallSite
   2: {
   3:     public ConstructorInfo ConstructorInfo { get; private set; }
   4:     public IServiceCallSite[] Parameters { get; private set; }
   5:  
   6:     public ConstructorCallSite(ConstructorInfo constructorInfo, IServiceCallSite[] parameters)
   7:     {
   8:         this.ConstructorInfo = constructorInfo;
   9:         this.Parameters = parameters;
  10:     }
  11:  
  12:     public Expression Build(Expression provider)
  13:     {
  14:         ParameterInfo[] parameters = this.ConstructorInfo.GetParameters();
  15:         return Expression.New(this.ConstructorInfo, this.Parameters.Select((p, index) => Expression.Convert(p.Build(provider), 
  16:             parameters[index].ParameterType)).ToArray());
  17:     }
  18:  
  19:     public object Invoke(ServiceProvider provider)
  20:     {
  21:         return this.ConstructorInfo.Invoke(this.Parameters.Select(p => p.Invoke(provider)).ToArray());
  22:     }
  23: }

ConstructorCallSite自身がサービスインスタンスを作成する論理は簡単ですが、ConstructorCallSiteオブジェクトを作成する方法自体は、最終的なコンストラクション関数をどのように選択するかという問題があるため、面倒です.この問題を専門に紹介し,構造関数を選択するために採用される2つの基本的な戦略をまとめた.
  • ServiceProviderは、コンストラクション関数のすべてのパラメータを提供します.
  • ターゲットコンストラクタのパラメータタイプ集合は、すべての有効コンストラクタパラメータタイプ集合のスーパーである.

  • ConstructorCallSiteの作成をサービスクラスのCreateConstructorCallSiteメソッドに定義します.GetConstructorとGetParameterCallSitesの2つの補助メソッドがあります.前者は正しいコンストラクタを選択するために使用され、後者は指定したコンストラクタの初期化パラメータに使用されるサービスCallSiteリストを作成します.
       1: internal class Service : IService
       2: {
       3:     private ConstructorCallSite CreateConstructorCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
       4:     {
       5:         ConstructorInfo constructor = this.GetConstructor(provider, callSiteChain);
       6:         if (null == constructor)
       7:         {
       8:             throw new InvalidOperationException("No avaliable constructor");
       9:         }
      10:         return new ConstructorCallSite(constructor, constructor.GetParameters().Select(p => provider.GetServiceCallSite(p.ParameterType, callSiteChain)).ToArray());                                              
      11: }
      12:  
      13:     private ConstructorInfo GetConstructor(ServiceProvider provider, ISet<Type> callSiteChain)
      14:     {
      15:         ConstructorInfo[] constructors = this.ServiceDescriptor.ImplementationType.GetConstructors()
      16:             .Where(c => (null != this.GetParameterCallSites(c, provider, callSiteChain))).ToArray();
      17:  
      18:         Type[] allParameterTypes = constructors.SelectMany(
      19:             c => c.GetParameters().Select(p => p.ParameterType)).Distinct().ToArray();
      20:  
      21:         return constructors.FirstOrDefault(
      22:             c => new HashSet<Type>(c.GetParameters().Select(p => p.ParameterType)).IsSupersetOf(allParameterTypes));
      23:     }
      24:  
      25:     private IServiceCallSite[] GetParameterCallSites(ConstructorInfo constructor,ServiceProvider provider,ISet<Type> callSiteChain)
      26:     {
      27:         ParameterInfo[] parameters = constructor.GetParameters();
      28:         IServiceCallSite[] serviceCallSites = new IServiceCallSite[parameters.Length];
      29:  
      30:         for (int index = 0; index < serviceCallSites.Length; index++)
      31:         {
      32:             ParameterInfo parameter = parameters[index];
      33:             IServiceCallSite serviceCallSite = provider.GetServiceCallSite(
      34:                 parameter.ParameterType, callSiteChain);
      35:             if (null == serviceCallSite && parameter.HasDefaultValue)
      36:             {
      37:                 serviceCallSite = new InstanceCallSite(parameter.DefaultValue);
      38:             }
      39:             if (null == serviceCallSite)
      40:             {
      41:                 return null;
      42:             }
      43:             serviceCallSites[index] = serviceCallSite;
      44:         }
      45:         return serviceCallSites;
      46:     }
      47:     //    
      48: }

    二、ライフサイクル管理のためのServiceCallSite


    サービスインスタンスが最終的にどのような提供方式を採用するかは、サービス登録時に採用されるライフサイクル管理モードにも関係しています.3つの異なるライフサイクル管理モード(Transient、Scoped、Singleton)は、それぞれ3つの異なるServiceCallSiteタイプに対応しています.これをTransiencellSite、ScopedCallSite、SingletonCallSiteと呼びます.
    TransientCallSiteでは、新しいオブジェクトを直接作成することで、サービスインスタンスを提供します.さらに、IDisposableインタフェースがサービスインスタンスに対応するタイプで実装されている場合、サービスプロバイダのTransientDisposableServicesプロパティに追加する必要があるというサービス回収に関する問題も考慮する必要があります.TransientCallSiteには、読み取り専用プロパティのServiceCallSiteは、サービスインスタンスの作成に実際に使用されるServiceCallSiteを表します.
       1: internal class TransientCallSite : IServiceCallSite
       2: {
       3:     public IServiceCallSite ServiceCallSite { get; private set; }
       4:     public TransientCallSite(IServiceCallSite serviceCallSite)
       5:     {
       6:         this.ServiceCallSite = serviceCallSite;
       7:     }
       8:  
       9:     public  Expression Build(Expression provider)
      10:     {
      11:         return Expression.Call(provider, "CaptureDisposable", null, this.ServiceCallSite.Build(provider));
      12:     }
      13:  
      14:     public  object Invoke(ServiceProvider provider)
      15:     {
      16:         return provider.CaptureDisposable(this.ServiceCallSite.Invoke(provider));
      17:     }
      18: }
      19:  
      20: internal class ServiceProvider : IServiceProvider, IDisposable
      21: {
      22:     
      23:     public object CaptureDisposable(object instance)
      24:     {
      25:         IDisposable disposable = instance as IDisposable;
      26:         if (null != disposable)
      27:         {
      28:             this.TransientDisposableServices.Add(disposable);
      29:         }
      30:         return instance;
      31:     }
      32:     //    
      33: }

    ScopedCallSiteは、サービスインスタンスを提供する際に、現在のServiceProviderのResolvedServicesプロパティにすでに提供されているサービスインスタンスが存在するかどうかを考慮する必要があります.すでに存在する場合は、繰り返し作成する必要はありません.新しく作成されたサービスインスタンスは、現在のServiceProviderのResolvedServicesプロパティに追加する必要があります.ScopedCallSiteは次のように定義されています.ServiceCallSiteプロパティは、現在のServiceProviderのResolvedServicesプロパティに保存されているサービスインスタンスを取得するために使用されます.
       1: internal class ScopedCallSite : IServiceCallSite
       2: {
       3:     public IService Service { get; private set; }
       4:     public IServiceCallSite ServiceCallSite { get; private set; }
       5:  
       6:     public ScopedCallSite(IService service, IServiceCallSite serviceCallSite)
       7:     {
       8:         this.Service = service;
       9:         this.ServiceCallSite = serviceCallSite;
      10:     }
      11:  
      12:     public virtual Expression Build(Expression provider)
      13:     {
      14:         var service = Expression.Constant(this.Service);
      15:         var instance = Expression.Variable(typeof(object), "instance");
      16:         var resolvedServices = Expression.Property(provider, "ResolvedServices");
      17:         var tryGetValue = Expression.Call(resolvedServices, "TryGetValue", null, service, instance);
      18:         var index = Expression.MakeIndex(resolvedServices, typeof(ConcurrentDictionary<IService, object>).GetProperty("Item"), new Expression[] { service});
      19:         var assign = Expression.Assign(index, this.ServiceCallSite.Build(provider));
      20:  
      21:         return Expression.Block(typeof(object),new[] { instance },Expression.IfThen(Expression.Not(tryGetValue),assign),index);
      22:     }
      23:  
      24:     public virtual object Invoke(ServiceProvider provider)
      25:     {
      26:         object instance;
      27:         return provider.ResolvedServices.TryGetValue(this.Service, out instance)
      28:             ? instance
      29:             : provider.ResolvedServices[this.Service] = this.ServiceCallSite.Invoke(provider);
      30:     }
      31: }

    実はSingletonとScopeの2つのモードは本質的に同等であり、あるServiceScopeの「単一の例」モードを表しています.これらの違いは、前者のServiceScopeがルートとしてのServiceProviderであり、後者が現在のServiceProviderである点です.したがってSingletonCallSiteはScopedCallSiteの派生クラスであり、具体的には以下のように定義されている.
       1: internal class SingletonCallSite : ScopedCallSite
       2: {
       3:     public SingletonCallSite(IService service, IServiceCallSite serviceCallSite) : 
       4:     base(service, serviceCallSite)
       5:     { }
       6:  
       7:     public override Expression Build(Expression provider)
       8:     {
       9:         return base.Build(Expression.Property(provider, "Root"));
      10:     }
      11:  
      12:     public override object Invoke(ServiceProvider provider)
      13:     {
      14:         return base.Invoke(provider.Root);
      15:     }
      16: }

    三、ServiceCallSiteの作成


    ServiceCallSiteの作成はIServiceインタフェースのCreateServiceSiteメソッドに反映されていますが、私たちのServicesクラスではこのメソッドはどのように実現されていますか?次のコード・クリップに示すように、サービス・インスタンスの作成に実際に使用されるServiceCallSiteは、現在のServiceDescriptorに基づいて作成され、ライフサイクル管理モードに基づいてそれに一致するServiceCallSiteが作成されます.
       1: internal class Service : IService
       2: {
       3:     public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
       4:     {
       5:         IServiceCallSite serviceCallSite = 
       6:             (null != this.ServiceDescriptor.ImplementationInstance)
       7:             ? new InstanceCallSite(this.ServiceDescriptor.ImplementationInstance)
       8:             : null;
       9:  
      10:         serviceCallSite = serviceCallSite?? 
      11:             ((null != this.ServiceDescriptor.ImplementationFactory)
      12:             ? new FactoryCallSite(this.ServiceDescriptor.ImplementationFactory)
      13:             : null);
      14:  
      15:         serviceCallSite = serviceCallSite ?? this.CreateConstructorCallSite(provider, callSiteChain);
      16:  
      17:         switch (this.Lifetime)
      18:         {
      19:             case ServiceLifetime.Transient: return new TransientCallSite(serviceCallSite);
      20:             case ServiceLifetime.Scoped: return new ScopedCallSite(this, serviceCallSite);
      21:             default: return new SingletonCallSite(this, serviceCallSite);
      22:         }
      23:     }
      24:     //        
      25: }

    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実現の秘密【漏れた細部を補う】