淮安做微信网站,网站源代码生成网站,超级外链吧外链代发,白杨seo教程ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的#xff0c;对于一个具体的ServiceDescriptor对象来说#xff0c;如果它的ImplementationInstance和ImplementationFactory属性均为Null#xff0c;那么ServiceProvider最终会利用其ImplementationT… ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的对于一个具体的ServiceDescriptor对象来说如果它的ImplementationInstance和ImplementationFactory属性均为Null那么ServiceProvider最终会利用其ImplementationType属性返回的真实类型选择一个适合的构造函数来创建最终的服务实例。我们知道服务服务的真实类型可以定义了多个构造函数那么ServiceProvider针对构造函数的选择会采用怎样的策略呢 目录 一、构造函数的选择 二、生命周期管理 ServiceScope与ServiceScopeFactory 三种生命周期管理模式 服务实例的回收 一、构造函数的选择 如果ServiceProvider试图通过调用构造函数的方式来创建服务实例传入构造函数的所有参数必须先被初始化最终被选择出来的构造函数必须具备一个基本的条件ServiceProvider能够提供构造函数的所有参数。为了让读者朋友能够更加真切地理解ServiceProvider在构造函数选择过程中采用的策略我们不让也采用实例演示的方式来进行讲解。 我们在一个控制台应用中定义了四个服务接口IFoo、IBar、IBaz和IGux以及实现它们的四个服务类Foo、Bar、Baz和Gux。如下面的代码片段所示我们为Gux定义了三个构造函数参数均为我们定义了服务接口类型。为了确定ServiceProvider最终选择哪个构造函数来创建目标服务实例我们在构造函数执行时在控制台上输出相应的指示性文字。 1: public interface IFoo {} 2: public interface IBar {} 3: public interface IBaz {} 4: public interface IGux {} 5: 6: public class Foo : IFoo {} 7: public class Bar : IBar {} 8: public class Baz : IBaz {} 9: public class Gux : IGux 10: { 11: public Gux(IFoo foo) 12: { 13: Console.WriteLine(Gux(IFoo)); 14: } 15: 16: public Gux(IFoo foo, IBar bar) 17: { 18: Console.WriteLine(Gux(IFoo, IBar)); 19: } 20: 21: public Gux(IFoo foo, IBar bar, IBaz baz) 22: { 23: Console.WriteLine(Gux(IFoo, IBar, IBaz)); 24: } 25: } 我们在作为程序入口的Main方法中创建一个ServiceCollection对象并在其中添加针对IFoo、IBar以及IGux这三个服务接口的服务注册针对服务接口IBaz的注册并未被添加。我们利用由它创建的ServiceProvider来提供针对服务接口IGux的实例究竟能否得到一个Gux对象呢如果可以它又是通过执行哪个构造函数创建的呢 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: new ServiceCollection() 6: .AddTransientIFoo, Foo() 7: .AddTransientIBar, Bar() 8: .AddTransientIGux, Gux() 9: .BuildServiceProvider() 10: .GetServicesIGux(); 11: } 12: } 对于定义在Gux中的三个构造函数来说ServiceProvider所在的ServiceCollection包含针对接口IFoo和IBar的服务注册所以它能够提供前面两个构造函数的所有参数。由于第三个构造函数具有一个类型为IBaz的参数这无法通过ServiceProvider来提供。根据我们上面介绍的第一个原则ServiceProvider能够提供构造函数的所有参数Gux的前两个构造函数会成为合法的候选构造函数那么ServiceProvider最终会选择哪一个呢 在所有合法的候选构造函数列表中最终被选择出来的构造函数具有这么一个特征每一个候选构造函数的参数类型集合都是这个构造函数参数类型集合的子集。如果这样的构造函数并不存在一个类型为InvalidOperationException的异常会被跑出来。根据这个原则Gux的第二个构造函数的参数类型包括IFoo和IBar而第一个构造函数仅仅具有一个类型为IFoo的参数最终被选择出来的会是Gux的第二个构造函数所有运行我们的实例程序将会在控制台上产生如下的输出结果。 1: Gux(IFoo, IBar) 接下来我们对实例程序略加改动。如下面的代码片段所示我们只为Gux定义两个构造函数它们都具有两个参数参数类型分别为IFooIBar和IBarIBaz。在Main方法中我们将针对IBaz/Baz的服务注册添加到创建的ServiceCollection上。 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: new ServiceCollection() 6: .AddTransientIFoo, Foo() 7: .AddTransientIBar, Bar() 8: .AddTransientIBaz, Baz() 9: .AddTransientIGux, Gux() 10: .BuildServiceProvider() 11: .GetServicesIGux(); 12: } 13: } 14: 15: public class Gux : IGux 16: { 17: public Gux(IFoo foo, IBar bar) {} 18: public Gux(IBar bar, IBaz baz) {} 19: } 对于Gux的两个构造函数虽然它们的参数均能够由ServiceProvider来提供但是并没有一个构造函数的参数类型集合能够成为所有有效构造函数参数类型集合的超集所以ServiceProvider无法选择出一个最佳的构造函数。如果我们运行这个程序一个InvalidOperationException异常会被抛出来控制台上将呈现出如下所示的错误消息。 1: Unhandled Exception: System.InvalidOperationException: Unable to activate type Gux. The following constructors are ambigious: 2: Void .ctor(IFoo, IBar) 3: Void .ctor(IBar, IBaz) 4: ... 二、生命周期管理 生命周期管理决定了ServiceProvider采用怎样的方式创建和回收服务实例。ServiceProvider具有三种基本的生命周期管理模式分别对应着枚举类型ServiceLifetime的三个选项Singleton、Scoped和Transient。对于ServiceProvider支持的这三种生命周期管理模式Singleton和Transient的语义很明确前者Singleton表示以“单例”的方式管理服务实例的生命周期意味着ServiceProvider对象多次针对同一个服务类型所提供的服务实例实际上是同一个对象而后者Transient则完全相反对于每次服务提供请求ServiceProvider总会创建一个新的对象。那么Scoped又体现了ServiceProvider针对服务实例怎样的生命周期管理方式呢 ServiceScope与ServiceScopeFactory ServiceScope为某个ServiceProvider对象圈定了一个“作用域”枚举类型ServiceLifetime中的Scoped选项指的就是这么一个ServiceScope。在依赖注入的应用编程接口中ServiceScope通过一个名为IServiceScope的接口来表示。如下面的代码片段所示继承自IDisposable接口的IServiceScope具有一个唯一的只读属性ServiceProvider返回确定这个服务范围边界的ServiceProvider。表示ServiceScope由它对应的工厂ServiceScopeFactory来创建后者体现为具有如下定义的接口IServiceScopeFactory。 1: public interface IServiceScope : IDisposable 2: { 3: IServiceProvider ServiceProvider { get; } 4: } 5: 6: public interface IServiceScopeFactory 7: { 8: IServiceScope CreateScope(); 9: } 若要充分理解ServiceScope和ServiceProvider之间的关系我们需要简单了解一下ServiceProvider的层级结构。除了直接通过一个ServiceCollection对象创建一个独立的ServiceProvider对象之外一个ServiceProvider还可以根据另一个ServiceProvider对象来创建如果采用后一种创建方式我们指定的ServiceProvider与创建的ServiceProvider将成为一种“父子”关系。 1: internal class ServiceProvider : IServiceProvider, IDisposable 2: { 3: private readonly ServiceProvider _root; 4: internal ServiceProvider(ServiceProvider parent) 5: { 6: _root parent._root; 7: } 8: //其他成员 9: } 虽然在ServiceProvider在创建过程中体现了ServiceProvider之间存在着一种树形化的层级结构但是ServiceProvider对象本身并没有一个指向“父亲”的引用它仅仅会保留针对根节点的引用。如上面的代码片段所示针对根节点的引用体现为ServiceProvider类的字段_root。当我们根据作为“父亲”的ServiceProvider创建一个新的ServiceProvider的时候父子均指向同一个“根”。我们可以将创建过程中体现的层级化关系称为“逻辑关系”而将ServiceProvider对象自身的引用关系称为“物理关系”右图清楚地揭示了这两种关系之间的转化。 由于ServiceProvider自身是一个内部类型我们不能采用调用构造函数的方式根据一个作为“父亲”的ServiceProvider创建另一个作为“儿子”的ServiceProvider但是这个目的可以间接地通过创建ServiceScope的方式来完成。如下面的代码片段所示我们首先创建一个独立的ServiceProvider并调用其GetServiceT方法获得一个ServiceScopeFactory对象然后调用后者的CreateScope方法创建一个新的ServiceScope它的ServiceProvider就是前者的“儿子”。 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: IServiceProvider serviceProvider1 new ServiceCollection().BuildServiceProvider(); 6: IServiceProvider serviceProvider2 serviceProvider1.GetServiceIServiceScopeFactory().CreateScope().ServiceProvider; 7: 8: object root serviceProvider2.GetType().GetField(_root, BindingFlags.Instance| BindingFlags.NonPublic).GetValue(serviceProvider2); 9: Debug.Assert(object.ReferenceEquals(serviceProvider1, root)); 10: } 11: } 如果读者朋友们希望进一步了解ServiceScope的创建以及它和ServiceProvider之间的关系我们不妨先来看看作为IServiceScope接口默认实现的内部类型ServiceScope的定义。如下面的代码片段所示ServiceScope仅仅是对一个ServiceProvider对象的简单封装而已。值得一提的是当ServiceScope的Dispose方法被调用的时候这个被封装的ServiceProvider的同名方法同时被执行。 1: { 2: private readonly ServiceProvider _scopedProvider; 3: public ServiceScope(ServiceProvider scopedProvider) 4: { 5: this._scopedProvider scopedProvider; 6: } 7: 8: public void Dispose() 9: { 10: _scopedProvider.Dispose(); 11: } 12: 13: public IServiceProvider ServiceProvider 14: { 15: get {return _scopedProvider; } 16: } 17: } IServiceScopeFactory接口的默认实现类型是一个名为ServiceScopeFactory的内部类型。如下面的代码片段所示ServiceScopeFactory的只读字段“_provider”表示当前的ServiceProvider。当CreateScope方法被调用的时候这个ServiceProvider的“子ServiceProvider”被创建出来并被封装成返回的ServiceScope对象。 1: internal class ServiceScopeFactory : IServiceScopeFactory 2: { 3: private readonly ServiceProvider _provider; 4: public ServiceScopeFactory(ServiceProvider provider) 5: { 6: _provider provider; 7: } 8: 9: public IServiceScope CreateScope() 10: { 11: return new ServiceScope(new ServiceProvider(_provider)); 12: } 13: } 三种生命周期管理模式 只有在充分了解ServiceScope的创建过程以及它与ServiceProvider之间的关系之后我们才会对ServiceProvider支持的三种生命周期管理模式Singleton、Scope和Transient具有深刻的认识。就服务实例的提供方式来说它们之间具有如下的差异 SingletonServiceProvider创建的服务实例保存在作为根节点的ServiceProvider上所有具有同一根节点的所有ServiceProvider提供的服务实例均是同一个对象。 ScopedServiceProvider创建的服务实例由自己保存所以同一个ServiceProvider对象提供的服务实例均是同一个对象。 Transient针对每一次服务提供请求ServiceProvider总是创建一个新的服务实例。 为了让读者朋友们对ServiceProvider支持的这三种不同的生命周期管理模式具有更加深刻的理解我们照例来做一个简单的实例演示。我们在一个控制台应用中定义了如下三个服务接口IFoo、IBar和IBaz以及分别实现它们的三个服务类(Foo、Bar和Baz)。 1: public interface IFoo {} 2: public interface IBar {} 3: public interface IBaz {} 4: 5: public class Foo : IFoo {} 6: public class Bar : IBar {} 7: public class Baz : IBaz {} 现在我们在作为程序入口的Main方法中创建了一个ServiceCollection对象并采用不同的生命周期管理模式完成了针对三个服务接口的注册(IFoo/Foo、IBar/Bar和IBaz/Baz分别Transient、Scoped和Singleton)。我们接下来针对这个ServiceCollection对象创建了一个ServiceProviderroot并采用创建ServiceScope的方式创建了它的两个“子ServiceProvider”child1和child2。 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: IServiceProvider root new ServiceCollection() 6: .AddTransientIFoo, Foo() 7: .AddScopedIBar, Bar() 8: .AddSingletonIBaz, Baz() 9: .BuildServiceProvider(); 10: IServiceProvider child1 root.GetServiceIServiceScopeFactory().CreateScope().ServiceProvider; 11: IServiceProvider child2 root.GetServiceIServiceScopeFactory().CreateScope().ServiceProvider; 12: 13: Console.WriteLine(ReferenceEquals(root.GetServiceIFoo(), root.GetServiceIFoo() {0},ReferenceEquals(root.GetServiceIFoo(), root.GetServiceIFoo())); 14: Console.WriteLine(ReferenceEquals(child1.GetServiceIBar(), child1.GetServiceIBar() {0},ReferenceEquals(child1.GetServiceIBar(), child1.GetServiceIBar())); 15: Console.WriteLine(ReferenceEquals(child1.GetServiceIBar(), child2.GetServiceIBar() {0},ReferenceEquals(child1.GetServiceIBar(), child2.GetServiceIBar())); 16: Console.WriteLine(ReferenceEquals(child1.GetServiceIBaz(), child2.GetServiceIBaz() {0},ReferenceEquals(child1.GetServiceIBaz(), child2.GetServiceIBaz())); 17: } 18: } 为了验证ServiceProvider针对Transient模式是否总是创建新的服务实例我们利用同一个ServiceProviderroot获取针对服务接口IFoo的实例并进行比较。为了验证ServiceProvider针对Scope模式是否仅仅在当前ServiceScope下具有“单例”的特性我们先后比较了同一个ServiceProviderchild1和不同ServiceProviderchild1和child2两次针对服务接口IBar获取的实例。为了验证具有“同根”的所有ServiceProvider针对Singleton模式总是返回同一个服务实例我们比较了两个不同child1和child2两次针对服务接口IBaz获取的服务实例。如下所示的输出结构印证了我们上面的论述。 1: ReferenceEquals(root.GetServiceIFoo(), root.GetServiceIFoo() False 2: ReferenceEquals(child1.GetServiceIBar(), child1.GetServiceIBar() True 3: ReferenceEquals(child1.GetServiceIBar(), child2.GetServiceIBar() False 4: ReferenceEquals(child1.GetServiceIBaz(), child2.GetServiceIBaz() True 服务实例的回收 ServiceProvider除了为我们提供所需的服务实例之外对于由它提供的服务实例它还肩负起回收之责。这里所说的回收与.NET自身的垃圾回收机制无关仅仅针对于自身类型实现了IDisposable接口的服务实例所谓的回收仅仅体现为调用它们的Dispose方法。ServiceProvider针对服务实例所采用的收受策略取决于服务注册时采用的生命周期管理模式具体采用的服务回收策略主要体现为如下两点 如果注册的服务采用Singleton模式由某个ServiceProvider提供的服务实例的回收工作由作为根的ServiceProvider负责后者的Dispose方法被调用的时候这些服务实例的Dispose方法会自动执行。 如果注册的服务采用其他模式Scope或者TransientServiceProvider自行承担由它提供的服务实例的回收工作当它的Dispose方法被调用的时候这些服务实例的Dispose方法会自动执行。 我们照例使用一个简单的实例来演示ServiceProvider针对不同生命周期管理模式所采用的服务回收策略。我们在一个控制台应用中定义了如下三个服务接口IFoo、IBar和IBaz以及三个实现它们的服务类Foo、Bar和Baz这些类型具有相同的基类Disposable。Disposable实现了IDisposable接口我们在Dispose方法中输出相应的文字以确定对象回收的时机。 1: public interface IFoo {} 2: public interface IBar {} 3: public interface IBaz {} 4: 5: public class Foo : Disposable, IFoo {} 6: public class Bar : Disposable, IBar {} 7: public class Baz : Disposable, IBaz {} 8: 9: public class Disposable : IDisposable 10: { 11: public void Dispose() 12: { 13: Console.WriteLine({0}.Dispose(), this.GetType()); 14: } 15: } 我们在作为程序入口的Main方法中创建了一个ServiceCollection对象并在其中采用不同的生命周期管理模式注册了三个相应的服务IFoo/Foo、IBar/Bar和IBaz/Baz分别采用Transient、Scoped和Singleton模式。我们针对这个ServiceCollection创建了一个ServiceProviderroot以及它的两个“儿子”child1和child2。在分别通过child1和child2提供了两个服务实例child1IFoo child2IBar/IBaz之后我们先后调用三个ServiceProviderchild1child2root的Dispose方法。 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: IServiceProvider root new ServiceCollection() 6: .AddTransientIFoo, Foo() 7: .AddScopedIBar, Bar() 8: .AddSingletonIBaz, Baz() 9: .BuildServiceProvider(); 10: IServiceProvider child1 root.GetServiceIServiceScopeFactory().CreateScope().ServiceProvider; 11: IServiceProvider child2 root.GetServiceIServiceScopeFactory().CreateScope().ServiceProvider; 12: 13: child1.GetServiceIFoo(); 14: child1.GetServiceIFoo(); 15: child2.GetServiceIBar(); 16: child2.GetServiceIBaz(); 17: 18: Console.WriteLine(child1.Dispose()); 19: ((IDisposable)child1).Dispose(); 20: 21: Console.WriteLine(child2.Dispose()); 22: ((IDisposable)child2).Dispose(); 23: 24: Console.WriteLine(root.Dispose()); 25: ((IDisposable)root).Dispose(); 26: } 27: } 该程序运行之后会在控制台上产生如下的输出结果。从这个结果我们不难看出由child1提供的两个采用Transient模式的服务实例的回收实在child1的Dispose方法执行之后自动完成的。当child2的Dispose方法被调用的时候对于由它提供的两个服务对象来说只有注册时采用Scope模式的Bar对象被自动回收了至于采用Singleton模式的Baz对象的回收工作是在root的Dispose方法被调用之后自动完成的。 1: child1.Dispose() 2: Foo.Dispose() 3: Foo.Dispose() 4: child2.Dispose() 5: Bar.Dispose() 6: root.Dispose() 7: Baz.Dispose() 了解ServiceProvider针对不同生命周期管理模式所采用的服务回收策略还会帮助我们正确的使用它。具体来说当我们在使用一个现有的ServiceProvider的时候由于我们并不能直接对它实施回收因为它同时会在其它地方被使用如果直接使用它来提供我们所需的服务实例由于这些服务实例可能会在很长一段时间得不到回收进而导致一些内存泄漏的问题。如果所用的是一个与当前应用具有相同生命周期ServiceProvider在应用终止的时候才会被回收的ServiceProvider而且提供的服务采用Transient模式这个问题就更加严重了这意味着每次提供的服务实例都是一个全新的对象但是它永远得不到回收。 为了解决这个问题我想很多人会想到一种解决方案那就是按照如下所示的方式显式地对提供的每个服务实例实施回收工作。实际上这并不是一种推荐的编程方式因为这样的做法仅仅确保了服务实例对象的Dispose方法能够被及时调用但是ServiceProvider依然保持着对服务实例的引用后者依然不能及时地被GC回收。 1: public void DoWork(IServiceProvider serviceProvider) 2: { 3: using (IFoobar foobar serviceProvider.GetServiceIFoobar()) 4: { 5: ... 6: } 7: } 或者 1: public void DoWork(IServiceProvider serviceProvider) 2: { 3: IFoobar foobar serviceProvider.GetServiceIFoobar(); 4: try 5: { 6: ... 7: } 8: finally 9: { 10: (foobar as IDisposable)?.Dispose(); 11: } 12: } 由于提供的服务实例总是被某个ServiceProvider引用着[1]直接提供服务实例的ServiceProvider或者是它的根所以服务实例能够被GC从内存及时回收的前提是引用它的ServiceProvider及时地变成垃圾对象。要让提供服务实例的ServiceProvider成为垃圾对象我们就必须创建一个新的ServiceProvider通过上面的介绍我们知道ServiceProvider的创建可以通过创建ServiceScope的方式来实现。除此之外为我们可以通过回收ServiceScope的方式来回收对应的ServiceProvider进而进一步回收由它提供的服务实例仅限Transient和Scoped模式。下面的代码片段给出了正确的编程方式。 1: public void DoWork(IServiceProvider serviceProvider) 2: { 3: using (IServiceScope serviceScope serviceProvider.GetServiceIServiceScopeFactory().CreateScope()) 4: { 5: IFoobar foobar serviceScope.ServiceProvider.GetServiceIFoobar(); 6: ... 7: } 8: } 接下来我们通过一个简单的实例演示上述这两种针对服务回收的编程方式之间的差异。我们在一个控制台应用中定义了一个继承自IDisposable的服务接口IFoobar和实现它的服务类Foobar。如下面的代码片段所示为了确认对象真正被GC回收的时机我们为Foobar定义了一个析构函数。在该析构函数和Dispose方法中我们还会在控制台上输出相应的指导性文字。 1: public interface IFoobar: IDisposable 2: {} 3: 4: public class Foobar : IFoobar 5: { 6: ~Foobar() 7: { 8: Console.WriteLine(Foobar.Finalize()); 9: } 10: 11: public void Dispose() 12: { 13: Console.WriteLine(Foobar.Dispose()); 14: } 15: } 在作为程序入口的Main方法中我们创建了一个ServiceCollection对象并采用Transient模式将IFoobbar/Foobar注册其中。借助于通过该ServiceCollection创建的ServiceProvider我们分别采用上述的两种方式获取服务实例并试图对它实施回收。为了强制GC试试垃圾回收我们显式调用了GC的Collect方法。 1: class Program 2: { 3: static void Main(string[] args) 4: { 5: IServiceProvider serviceProvider new ServiceCollection() 6: .AddTransientIFoobar, Foobar() 7: .BuildServiceProvider(); 8: 9: serviceProvider.GetServiceIFoobar().Dispose(); 10: GC.Collect(); 11: 12: Console.WriteLine(----------------); 13: using (IServiceScope serviceScope serviceProvider.GetServiceIServiceScopeFactory().CreateScope()) 14: { 15: serviceScope.ServiceProvider.GetServiceIFoobar(); 16: } 17: GC.Collect(); 18: 19: Console.Read(); 20: } 21: } 该程序执行之后会在控制台上产生如下所示的输出结果。从这个结果我们可以看出如果我们使用现有的ServiceProvider来提供所需的服务实例后者在GC进行垃圾回收之前并不会从内存中释放。如果我们利用现有的ServiceProvider创建一个ServiceScope并利用它所在的ServiceProvider来提供我们所需的服务实例GC是可以将其从内存中释放出来的。 1: Foobar.Dispose() 2: ---------------- 3: Foobar.Dispose() 4: Foobar.Finalize() [1] 对于分别采用 Scoped和Singleton模式提供的服务实例当前ServiceProvider和根ServiceProvider分别具有对它们的引用。如果采用Transient模式只有服务类型实现了IDisposable接口当前ServiceProvider才需要对它保持引用以完成对它们的回收否则没有任何一个ServiceProvider保持对它们的引用。 ASP.NET Core中的依赖注入1控制反转IoCASP.NET Core中的依赖注入2依赖注入DIASP.NET Core中的依赖注入3服务注册与提取ASP.NET Core中的依赖注入4构造函数的选择与生命周期管理ASP.NET Core中的依赖注入5ServicePrvider实现揭秘【总体设计】ASP.NET Core中的依赖注入5ServicePrvider实现揭秘【解读ServiceCallSite】ASP.NET Core中的依赖注入5ServicePrvider实现揭秘【补充漏掉的细节】 作者蒋金楠 微信公众账号大内老A 微博www.weibo.com/artech 如果你想及时得到个人撰写文章以及著作的消息推送或者想看看个人推荐的技术资料可以扫描左边二维码或者长按识别二维码关注个人公众号原来公众帐号蒋金楠的自媒体将会停用。 本文版权归作者和博客园共有欢迎转载但未经作者同意必须保留此段声明且在文章页面明显位置给出原文连接否则保留追究法律责任的权利。 原文链接