[关闭]
@dasajia2lang 2019-03-12T03:22:54.000000Z 字数 8028 阅读 402
#  Core的DI容器简介

###DI注入的几种表现形式

 - 构造函数注入
  构造器注入就在在构造函数中借助参数将依赖的对象注入到创建的对象之中。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性Bar上,针对该属性的初始化实现在构造函数中,具体的属性值由构造函数的传入的参数提供。当DI容器通过调用构造函数创建一个Foo对象之前,需要根据当前注册的类型匹配关系以及其他相关的注入信息创建并初始化参数对象。

```
    public class Foo : IFoo
    {
        public Foo(IBar bar)
        {
            Bar = bar;
        }

        public IBar Bar { get; private set; }
    }
```
 - 属性注入
  如果依赖直接体现为类的某个属性,并且该属性不是只读的,我们可以让DI容器在对象创建之后自动对其进行赋值进而达到依赖自动注入的目的。一般来说,我们在定义这种类型的时候,需要显式将这样的属性标识为需要自动注入的依赖属性,以区别于该类型的其他普通的属性。如下面的代码片段所示,Foo类中定义了两个可读写的公共属性Bar和Baz,我们通过标注InjectionAttribute特性的方式将属性Baz设置为自动注入的依赖属性。对于由DI容器提供的Foo对象,它的Baz属性将会自动被初始化。
```
    public class FooPro : IFoo
    {
        [Injection]
        public IBar Bar { get; set; }

        [Injection]
        public IBaz Baz { get; set; }
    }
```

 - 方法注入
体现依赖关系的字段或者属性可以通过方法的形式初始化。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性上,针对该属性的初始化实现在Initialize方法中,具体的属性值由构造函数的传入的参数提供。我们同样通过标注特性(InjectionAttribute)的方式将该方法标识为注入方法。DI容器在调用构造函数创建一个Foo对象之后,它会自动调用这个Initialize方法对只读属性Bar进行赋值。在调用该方法之前,DI容器会根据预先注册的类型映射和其他相关的注入信息初始化该方法的参数。

```
    public class FooMethed
    {
        public IBar Bar { get; private set; }

        [Injection]
        public void Initialize(IBar bar)
        {
            this.Bar = bar;
        }
    }
```
---

###Core的依赖注入--服务的注册和发现

 1. ServiceProvider与ServiceDescriptor
IServiceProvider只有一个方法GetService().
```
    public interface IServiceProvider
    {
        object GetService(Type serviceType);
    }
```
ServiceDescriptor是注入的一个载体。所有的对于服务的注入最终都会转变成一个ServiceDescriptor注入到DI容器里面。
它的定义为:
```
   public class ServiceDescriptor
   {
       public ServiceDescriptor(Type serviceType, object instance);
       public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
       public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
       //使用Type注入时,这里存储的是注入的服务的Type
       public Type                                ServiceType {  get; }
       //注入的生命周期
       public ServiceLifetime                     Lifetime {  get; }
       //这里存储的是注入的实例的Type(实例注入的时候这里为空)
       public Type                                ImplementationType {  get; }
       //当实例注入时,这里存储的是注入的类的实例(使用的时候应该就会存在一个装箱拆箱的性能损失)
       public object                              ImplementationInstance {  get; }
       //工厂注入的实例(只有通过工厂方法注入的时候才会赋值)
       public Func<IServiceProvider, object>      ImplementationFactory {  get; }      
   }
```
###服务的注册
服务注册的方式一般根据Lifetime分为3类。
**Single**(ServiceProvider创建的服务实例保存在作为根节点的ServiceProvider上,所有具有同一根节点的所有ServiceProvider提供的服务实例均是同一个对象)
**Scoped**(ServiceProvider创建的服务实例由自己保存,所以同一个ServiceProvider对象提供的服务实例均是同一个对象),
**Transient**(每获取一次服务就创建一个新的实例)
具体是调用:
```
 public static class ServiceCollectionExtensions
   {
       public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService: class;
      //其他AddScoped<TService>重载

       public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
      //其他AddSingleton<TService>重载

       public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService: class;
       //其他AddTransient<TService>重载
   }
```
 具体讲一下工厂方法注入:
```
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
```
工厂委托:```Func<IServiceProvider, TService> implementationFactory```.
输入一个IServiceProvider的作用我目前能想到的就是因为你在构造你的TService时候有可能要用到你之前注入到Collection里面的服务,所以你可以通过输入参数IServiceProvider去进行获取。

```
//工厂方式注入
collection.AddSingleton<IFoo>(_ =>new Foo(new Bar()));
```
当注入同一个服务时,后面注入的实例会把前面的给覆盖掉。如果想获取所有注册的实例请使用获取服务的集合
###获取一个服务的集合
```
    public static class ServiceProviderExtensions
    {
        public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
        public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
    }
```
其实就是返回一个IEnumerable集合,当你注册多个服务的时候。

###构造函数的选取
当通过Type进行注册的时候,如果一个接口的实例有多个构造函数,默认选取的是哪个构造函数?

原则:构造函数的所有参数必须要先被初始化,最终选择的构造函数是能通过ServiceProvider中获取到的所有的参数的构造函数。
```
public class FooConstruction : IFoo
    {

        public FooConstruction()
        {
            Console.WriteLine("Using No Parmeter Construction!!");
        }

        public FooConstruction(IBar bar)
        {
            Console.WriteLine("Using IBar Construction!!");
        }

        public FooConstruction(IBar bar,IBaz baz)
        {
            Console.WriteLine("Using IBar,IBaz Construction!!");
        }

        public void SayFoo()
        {
            Console.WriteLine("It is FooConstruction!!");
        }
    }

            collection.AddSingleton(typeof(IFoo), typeof(FooConstruction));
            //无参构造函数
            var foo1 = collection.BuildServiceProvider().GetRequiredService<IFoo>();
            collection.AddSingleton<IBar>(new Bar());
            //参数为IBar的构造函数
            var foo2 = collection.BuildServiceProvider().GetRequiredService<IFoo>();
            collection.AddSingleton<IBaz>(new Baz());
            //参数为IBar和参数为IBaz的构造函数
            var foo3 = collection.BuildServiceProvider().GetRequiredService<IFoo>(); 
```
虽然IServiceProvider能够提供构造函数所有的参数,但是当没有一个构造函数的参数是其他所有构造函数的参数的超级时,则也会报错!!!

```
    /// <summary>
    /// 构造函数的选取
    /// </summary>
    public class FooNewConstruction : IFoo
    {

        public FooNewConstruction()
        {
            Console.WriteLine("Using No Parmeter Construction!!");
        }

        public FooNewConstruction(IBar bar)
        {
            Console.WriteLine("Using IBar Construction!!");
        }

        public FooNewConstruction(IBaz baz)
        {
            Console.WriteLine("Using IBaz Construction!!");
        }

        public void SayFoo()
        {
            Console.WriteLine("It is FooConstruction!!");
        }
    }

            collection.Clear();
            collection.AddSingleton<IBar>(new Bar()).AddSingleton<IBaz>(new Baz());
            collection.AddSingleton(typeof(IFoo),typeof(FooNewConstruction));
            //报错
            var foo4= collection.BuildServiceProvider().GetRequiredService<IFoo>();

```
虽然能够获取到IBar和IBaz,但是在调用FooNewConstruction时,不能够获得唯一的一个构造函数,因为程序不知道要调用IBar的构造函数,还是IBaz的构造函数,这就是为什么要一个超集的原因!!

###服务周期(生命周期管理)
ServiceProvider具有三种基本的生命周期管理模式,分别对应着枚举类型ServiceLifetime的三个选项(Singleton、Scoped和Transient)。对于ServiceProvider支持的这三种生命周期管理模式,Singleton和Transient的语义很明确,前者(Singleton)表示以“单例”的方式管理服务实例的生命周期,意味着ServiceProvider对象多次针对同一个服务类型所提供的服务实例实际上是同一个对象;而后者(Transient)则完全相反,对于每次服务提供请求,ServiceProvider总会创建一个新的对象。那么Scoped又体现了ServiceProvider针对服务实例怎样的生命周期管理方式呢?

ServiceScope与ServiceScopeFactory
ServiceScope为每一个IServiceProvider圈定了一个作用域。换句话说:一个IServiceProvider有着一个对应的ServiceScpe。
ServiceProvider可以根据另外一个ServiceProvider进行创建。
ServiceScpe类
```
 {
       private readonly ServiceProvider _scopedProvider;
       public ServiceScope(ServiceProvider scopedProvider)
       {
           this._scopedProvider = scopedProvider;
       }

       public void Dispose()
       {
           _scopedProvider.Dispose();
       }

       public IServiceProvider ServiceProvider
       {
           get {return _scopedProvider; }
       }
   }

  internal class ServiceScopeFactory : IServiceScopeFactory
   {
       private readonly ServiceProvider _provider;
       public ServiceScopeFactory(ServiceProvider provider)
       {
           _provider = provider;
       }

       public IServiceScope CreateScope()
       {
           return new ServiceScope(new ServiceProvider(_provider));
       }
   }

```
就是通过Scoped注入的服务,不同的Provider获取的是不同的值。
####服务的释放
如果我们在使用Transient和Scoped方式注入服务时,如果我们我们不显示的去回收的话,就有可能造成内存泄漏。
每一次提取一个service都会创建一个新的服务,但是IServiceProvider保持着对新创建的服务的引用导致服务不能够被GC回收。

服务回收的次序:
Singleton注册服务只有在 root ServiceProvider被回收时才会被回收。
Transient和Scoped方式注册的时候,在其创建的SersviceProvider被回收时,会被自动回收。

```
   public interface IFoo {}
   public interface IBar {}
   public interface IBaz {}

   public class Foo : Disposable, IFoo {}
   public class Bar : Disposable, IBar {}
   public class Baz : Disposable, IBaz {}

   public class Disposable : IDisposable
   {
       public void Dispose()
       {
           Console.WriteLine("{0}.Dispose()", this.GetType());
       }
   }

   class Program
   {
       static void Main(string[] args)
       {
           IServiceProvider root = new ServiceCollection()
               .AddTransient<IFoo, Foo>()
               .AddScoped<IBar, Bar>()
               .AddSingleton<IBaz, Baz>()
               .BuildServiceProvider();
           IServiceProvider child1 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
           IServiceProvider child2 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;

           child1.GetService<IFoo>();
           child1.GetService<IFoo>();
           child2.GetService<IBar>();
           child2.GetService<IBaz>();

           Console.WriteLine("child1.Dispose()");
           ((IDisposable)child1).Dispose();

           Console.WriteLine("child2.Dispose()");
           ((IDisposable)child2).Dispose();

           Console.WriteLine("root.Dispose()");
           ((IDisposable)root).Dispose();
       }
   }

```
结果:
```
   1: child1.Dispose()
   2: Foo.Dispose()
   3: Foo.Dispose()
   4: child2.Dispose()
   5: Bar.Dispose()
   6: root.Dispose()
   7: Baz.Dispose()

```
   有可能会想到,我们每个服务都自己继承IDispose接口,然后使用的时候用using包裹着。
   public void Do()
   {
      using(IFoo foo=serviceProvider.GetService<IFoo>()){}
   }
   或者是try finally{((IDispose)foo).Dispose() }

   这些都是不推荐的编程方法,这样是没什么用的,因为他们的ServiceProvider还保持着对于服务的引用。

   只有当ServiceProvider进行释放时才会正真的释放。


     class Program
     {
       static void Main(string[] args)
       {

           IServiceProvider serviceProvider = new ServiceCollection()

               .AddTransient<IFoobar, Foobar>()

               .BuildServiceProvider();

           serviceProvider.GetService<IFoobar>().Dispose();
           GC.Collect();

           Console.WriteLine("----------------");
           using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
           {
               serviceScope.ServiceProvider.GetService<IFoobar>();
           }
           GC.Collect();

           Console.Read();
       }
    }
结果:
    Foobar.Dispose()
    ----------------
    Foobar.Dispose()
    Foobar.Finalize()
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注