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実現の秘密【漏れた細部を補う】
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を作成する目的は、現在の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実現の秘密【漏れた細部を補う】
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実現の秘密【漏れた細部を補う】