ASP.NET CORE Configuration and Option 组件(c#,IConfiguration,IOption)

说明

个人学习记录,可能有理解偏差,有误地方,望大佬可指正

Configuration

Configuration接口UML图

在这里插入图片描述

Configuration 应为 源的统一构建助手,源的构建配置,源的数据提供出口,源的统一数据提供总出口,源的节点描述(源指的是数据源)

  • IConfigurationBuilder:源的统一构建助手

  • 源的统一构建助手必然就应该负责构建源,以及构建源的统一数据提供总出口

  • 也就是该接口中依赖的 IConfigurationSource(构建源) 和 IConfigurationRoot(源的统一数据提供总出口)

  • IConfigurationSource:源的构建配置

  • 实现IConfigurationSource接口,并且在实现类中添加构建 源的数据提供出口类(IConfigurationProvider) 的必要参数,再使用该参数进行构建 源的数据提供出口类(IConfigurationProvider)

  • IConfigurationProvider:源的数据提供出口

  • 实现IConfigurationProvider接口,主要是加载对应 path 下的内容,尝试获取对应key的值以及set对应key的值。

  • IConfigurationRoot:源的统一数据提供总出口

  • 实现IConfigurationRoot接口,可以拥有所有源的能力。每个源的load()会在IConfigurationRoot实现类的构造函数中被触发,而IConfigurationRoot接口又实现了IConfiguration接口,所以IConfiguration接口提供了获取源节点描述信息以及源对应key的value信息的能力,所以在IConfigurationRoot接口实现类是源的统一数据提供总出口

  • IConfiguration 和 IConfigurationSection:源的节点描述

  • IConfiguration 和 IConfigurationSection构成了源节点的描述。为什么这样说呢?

  • IConfigurationSection 其实就是可以看作源的 节点或子节点描述,要注意的是IConfiguration实际上还实现了IConfiguration,而IConfiguration身上是拥有获取子节点的能力的(GetChildren()方法)。通过IConfiguration中的GetSection()方法获取到某个节点或子节点的描述,再通过 string? this[string key] { get; set; }获取对应值

  • IConfiguration 更加可以看作 源的数据获取器

    • string? this[string key] { get; set; }获取某个节点对应key的value值
    • GetSection(string key)获取某个节点的节点描述,可以获取到他的所有children的节点描述,再获取他的值
  • 实际上IConfiguration和IConfigurationSection最终都会调回到IConfigurationProvider接口的tryGet()方法

  • 可以理解为下图:
    在这里插入图片描述
    在这里插入图片描述

总结:

  • 对内的核心类其实就是IConfigurationBuilder接口实现类以及 IConfigurationRoot接口实现类
  • IConfigurationBuilder接口 实现了IConfiguration接口,可以获取到 源的整体节点描述,并存储了所有的 源的数据提供出口(IConfigurationProvide)
  • IConfigurationBuilder接口则是通过IConfigurationSource构建 IConfigurationProvider,再赋值到IConfigurationRoot接口实现类中,完成源的统一构建助手的任务
  • 对外的核心类其实就是 IConfiguration接口(注意:IConfigurationRoot也实现了IConfiguration接口 )
  • 提供了一系列的查询方法
    • string? this[string key] { get; set; }
    • GetSection(string key):IConfigurationSection
    • GetChildren():IEnumerable<IConfigurationSection>

Q&A

  • 如何获取Configuration中的数据

    • IConfiguration 中[key]直接获取
    • IConfigurationSection 中[key]直接获取
    • IConfiguration 中getchilder再进行,[key]获取
  • 我们常见的builder.Configuration.AddJsonFile他到底完成的是一件什么样子的事

    • 实际上应该就是向 IConfigurationBuilder中的IList<IConfigurationSource> Sources加入 jsonConfigurationSource ,在调用IConfigurationBuilder#build()方法的时候就会创建对应的 IConfigurationProvider
  • Configuration整一个东西的整体样貌

    在这里插入图片描述在这里插入图片描述

Option

简介

关于IOption

  • IOption被称为选项模型,它是在.netcore中引入的。它的作用是给我们的应用程序提供配置,这个配置就是一个个的对象模型。

关于“依赖注入”和“选项模型”:

  • “选项模型”本身是构筑在“依赖注入”框架上的,所以我们要想使用“选项模型”就必须先引入“依赖注入”框架。

关于“配置”和“选项模型”:

  • IConfigurationIOption在.netcore中被称为配置选项,前者表示配置,后者表示选项模型。它们二者在.netcore中是一对基石。虽然它们二者的关系紧密,而且选项模型的数据来源大多来自配置,但它们二者之间并没有直接的依赖关系,也就是说它们二者任何一个都可以单独拿出来使用。

关于选项模型的包:

  • 选项模型本身就一个nuget包:Microsoft.Extensions.Options

  • 选项模型是运行在依赖注入框架上的,所以它引用“依赖注入”框架的抽象包

``Microsoft.Extensions.DependencyInjection.Abstractions`

  • 如果是控制台程序,为了能使用“选项模型”,我们还要再手动引入“依赖注入”框架的实现包:

Microsoft.Extensions.DependencyInjection

  • 如果我们想让“配置”为“选项模型”提供数据来源,那么我们需要手动引入扩展包:

Microsoft.Extensions.Options.ConfigurationExtensions

  • 如果想用已经定义好的一些参数配置校验,可以手动引入扩展包:

Microsoft.Extensions.Options.DataAnnotations

  • 在使用IConfiguration作为数据源的时候,如果需要用到一些JSON扩展到东西,可以引入JSON配置包:

Microsoft.Extensions.Configuration.Json

使用介绍

通过注入IConfiguration的方式读取配置

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<BookOptions>(Configuration.GetSection(BookOptions.Book));
    }
}

手动绑定

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        var bookOptions1 = new BookOptions();
        Configuration.GetSection(BookOptions.Book).Bind(bookOptions1);
    }
}

通过强类型注入来获取到配置对象

通过Options接口,我们可以读取依赖注入容器中的Options。常用的有三个接口:

  • IOptions<TOptions>
  • IOptionsSnapshot<TOptions>
  • IOptionsMonitor<TOptions>
IOptions(UnnameOptionsManager)
  • 该接口对象实例生命周期为 Singleton,因此能够将该接口注入到任何生命周期的服务中
  • 当该接口被实例化后,其中的选项值将永远保持不变,即使后续修改了与选项进行绑定的配置,也永远读取不到修改后的配置值
  • 不支持命名选项(Named Options)
public class TestController : ControllerBase
{
   private readonly BookOptions _bookOptions;

   public TestController(IOptions<BookOptions> bookOptions)
   {
       // bookOptions.Value 始终是程序启动时加载的配置,永远不会改变
       _bookOptions = bookOptions.Value;
   }
}
IOptionsSnapshot(OptionManager)
  • 该接口被注册为 Scoped,因此该接口无法注入到 Singleton 的服务中,只能注入到 Transient 和 Scoped 的服务中。
  • 在作用域中,创建IOptionsSnapshot<TOptions>对象实例时,从配置中读取最新选项值作为快照,在单前作用域中始终使用该快照。
  • 支持命名选项
public class ValuesController : ControllerBase
{
   private readonly BookOptions _bookOptions;

   public ValuesController(IOptionsSnapshot<BookOptions> bookOptionsSnapshot)
   {
       // bookOptions.Value 是 Options 对象实例创建时读取的配置快照
       _bookOptions = bookOptionsSnapshot.Value;
   }
}
IOptionsMonitor(OptionsMonitor)
  • 该接口除了可以查看TOptions的值,还可以监控TOptions配置的更改。
  • 该接口被注册为 Singleton,因此能够将该接口注入到任何生命周期的服务中
  • 每次读取选项值时,都是从配置中读取最新选项值
  • 支持:
    (1) 命名选项
    (2)重新加载配置(CurrentValue),并当配置发生更改时,进行通知(OnChange
    (3)缓存与缓存失效 (IOptionsMonitorCache<TOptions>)
public class ValuesController : ControllerBase
{
   private readonly IOptionsMonitor<BookOptions> _bookOptionsMonitor;

   public ValuesController(IOptionsMonitor<BookOptions> bookOptionsMonitor)
   {
       // _bookOptionsMonitor.CurrentValue 的值始终是最新配置的值
       _bookOptionsMonitor = bookOptionsMonitor;
   }
}

Option接口UML图

总览

在这里插入图片描述

整个选项模型的接口,我认为可以分为4部分,分别是UnnameOptionsManager,OptionFactory,OptionManager,OptionsMonitor

  • OptionFactory是用与创建 IOption 中的Options。

  • UnnameOptionsManager,OptionManager,OptionsMonitor均依赖于IOptionFactory,便于获取 IOption 中的TOptions

  • UnnameOptionsManager,OptionManager,OptionsMonitor的职责: 在DI以及OptionFactory的配合下 为获取IOption 中的TOptions扩展功能,每种实现类扩展了什么功能在使用介绍 中已有介绍,可往回看。

UnnameOptionsManager

在这里插入图片描述

  • UnnameOptionsManager 中主要的一个获取 IOptions中的TOptions

  • 与 DI 打配合 创建单例的TOptions

  • 为什么说UnnamedOptionsManager不支持nameOptions功能

  • 进入UnnamedOptionsManager的源码Get方法可得:

    可以清楚的看到在调用IOptionsFactory的create方法的时候,只传入了Options.DefaultName默认名称

    internal sealed class UnnamedOptionsManager< TOptions> : IOptions<TOptions> where TOptions : class{
      		//....
          private readonly IOptionsFactory<TOptions> _factory;
      		//....
      		public TOptions Value{
          get {
            TOptions options = this._value;
            if ((object) options != null)
              return options;
         		//.....
              return this._value ?? 
                (this._value = this._factory.Create(Microsoft.Extensions.Options.Options.DefaultName));
          }
        }
    }
    

OptionManager

在这里插入图片描述

public class OptionsManager<TOptions> : IOptions<TOptions>,IOptionsSnapshot<TOptions>where TOptions : class{
   //....
   private readonly IOptionsFactory<TOptions> _factory;
   private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
 	//....
 	public virtual TOptions Get(string? name)
   {
     //如果name为空,则赋默认名称
     if (name == null)
       name = Microsoft.Extensions.Options.Options.DefaultName;
     TOptions options;
     //尝试从缓存中获取对应的 OptionsCache<TOptions> 
     if (!this._cache.TryGetValue(name, out options))
     {
       IOptionsFactory<TOptions> localFactory = this._factory;
       string localName = name;
       //缓存中没有则创建并且保存到缓存中
       options = this._cache.GetOrAdd(name, (Func<TOptions>) (() => localFactory.Create(localName)));
     }
     return options;
   }
}

首先需要说明的是OptionsManager和DI打配合的时候,OptionsManager是注册为Scope。如果构造方法中引入的是IOptionsSnapshot<TOptions>,可以说明的是:

  • 不同作用域下的TOptions不是同一个对象,同一个作用域下TOptions是同一个对象
  • 为什么同一个作用域下TOptions是同一个对象呢?
    • 可以看到上面源码,在IOptonsFactory中调用create方法之后,会缓存到_cache中,每次获取,都会从缓存中先获取(注意这里就会产生一个问题:如果配置更新了呢,这里是无感知的,且无重新加载的方法调用,无法获取到配置的最新值)
  • 每个作用域都会有他自己的_cache
  • 支持 nameOptions
  • 通过名称做区分,缓存多个相同的TOptions

OptionsMonitor

在这里插入图片描述

首先需要说明的是OptionsMonitor和DI打配合的时候,OptionsMonitor是注册为单例。如果构造方法中引入的是IOptionsMonitor<TOptions>,可以说明的是:

  • 他获取的是全局TOptions对象
  • OptionsManager中的 _cache 也是DI 中单例注册,全局缓存保存
  • 支持:
  • 命名选项
    • 通过名称做区分,缓存多个相同的TOptions
  • 重新加载配置(CurrentValue),并当配置发生更改时,进行通知(OnChange)【暂不做介绍,还没深入了解】
  • 缓存与缓存失效 (IOptionsMonitorCache<TOptions>)
public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>,IDisposable where TOptions : class
 {
  
   private readonly IOptionsMonitorCache<TOptions> _cache;
   private readonly IOptionsFactory<TOptions> _factory;
   private readonly List<IDisposable> _registrations = new List<IDisposable>();
   internal event Action<TOptions, string>? _onChange;
 
 	//...
 	 public virtual TOptions Get(string? name1){
    //自定义 _cache 
    if (!(this._cache is OptionsCache<TOptions> cache)){
      string localName = name1 ?? Microsoft.Extensions.Options.Options.DefaultName;
      IOptionsFactory<TOptions> localFactory = this._factory;
      return this._cache.GetOrAdd(localName, (Func<TOptions>) (() => localFactory.Create(localName)));
    }
    //非自定义缓存 OptionsCache,
    string name = name1;
    IOptionsFactory<TOptions> factory1 = this._factory;
    return cache.GetOrAdd<IOptionsFactory<TOptions>>(
       name, 
       (Func<string, IOptionsFactory<TOptions>, TOptions>) ((name2, factory) => factory.Create(name2)), factory1);
   }
 	//...
}

OptionsFactory

在这里插入图片描述

IOptionsFactory的指责就是 创建TOptions, 那他这咋依赖了IConfigureOptions<TOptions>,IPostConfigureOptions<TOptions>,IVaildateOptions<TOptions>,他们分别的作用是:

  • IConfigureOptions<TOptions>
  • 配置TOptions
  • IPostConfigureOptions<TOptions>
  • 配置TOptions后执行的后置操作
  • IVaildateOptions<TOptions>
  • 执行 配置TOptions以及配置TOptions后执行的后置操作 后,执行的校验操作

依据:

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions>where TOptions : class{

   private readonly IConfigureOptions<TOptions>[] _setups;
   private readonly IPostConfigureOptions<TOptions>[] _postConfigures;
   private readonly IValidateOptions<TOptions>[] _validations;
 
 	//.....
		public TOptions Create(string name){
     //反射创建TOptions对象
     TOptions instance = this.CreateInstance(name);
     foreach (IConfigureOptions<TOptions> setup in this._setups){
       // 判断IConfigureOptions是否是IConfigureNamedOptions(也就是nameOptions,支持创建名称)
       if (setup is IConfigureNamedOptions<TOptions> configureNamedOptions)
         configureNamedOptions.Configure(name, instance);
       //判断name是否和默认名称相同
       else if (name == Microsoft.Extensions.Options.Options.DefaultName)
         setup.Configure(instance);
     }
     //执行IConfigureOptions配置之后,进行后置配置操作
     foreach (IPostConfigureOptions<TOptions> postConfigure in this._postConfigures)
       postConfigure.PostConfigure(name, instance);
     //查看校验方法是否为0
     if (this._validations.Length != 0)
     {
       List<string> failureMessages = new List<string>();
       //对配置进行校验
       foreach (IValidateOptions<TOptions> validation in this._validations)
       {
         ValidateOptionsResult validateOptionsResult = validation.Validate(name, instance);
         if (validateOptionsResult != null && validateOptionsResult.Failed)
           failureMessages.AddRange(validateOptionsResult.Failures);
       }
       if (failureMessages.Count > 0)
         throw new OptionsValidationException(name, typeof (TOptions), (IEnumerable<string>) failureMessages);
     }
     return instance;
   }
 //.....
}

OptionsCache

public class OptionsCache<TOptions> : IOptionsMonitorCache<TOptions>where TOptions : class{
   	
  	//key值为TOptions的nameOpton配置,value为对应TOptions的懒加载
    private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(1, 31, (IEqualityComparer<string>) StringComparer.Ordinal);


    public virtual TOptions GetOrAdd(string? name1, Func<TOptions> createOptions1)
    {
      ThrowHelper.ThrowIfNull((object) createOptions1, nameof (createOptions));
      if (name1 == null)
        name1 = Microsoft.Extensions.Options.Options.DefaultName;
      return this._cache.GetOrAdd<Func<TOptions>>(name1, (Func<string, Func<TOptions>, Lazy<TOptions>>) ((name2, createOptions2) => new Lazy<TOptions>(createOptions2)), createOptions1).Value;
    }

    internal TOptions GetOrAdd<TArg>(
      string? name1,
      Func<string, TArg, TOptions> createOptions,
      TArg factoryArgument)
    {
      if (!(this.GetType() != typeof (OptionsCache<TOptions>)))
        return this._cache.GetOrAdd<(Func<string, TArg, TOptions>, TArg)>(name1 ?? Microsoft.Extensions.Options.Options.DefaultName, (Func<string, (Func<string, TArg, TOptions>, TArg), Lazy<TOptions>>) ((name2, arg) => new Lazy<TOptions>((Func<TOptions>) (() => arg.createOptions(name2, arg.factoryArgument)))), (createOptions, factoryArgument)).Value;
      string localName = name1;
      Func<string, TArg, TOptions> localCreateOptions = createOptions;
      TArg localFactoryArgument = factoryArgument;
      return this.GetOrAdd(name1, (Func<TOptions>) (() => localCreateOptions(localName ?? Microsoft.Extensions.Options.Options.DefaultName, localFactoryArgument)));
    }

   
    internal bool TryGetValue(string? name, [MaybeNullWhen(false)] out TOptions options)
    {
      Lazy<TOptions> lazy;
      if (this._cache.TryGetValue(name ?? Microsoft.Extensions.Options.Options.DefaultName, out lazy))
      {
        options = lazy.Value;
        return true;
      }
      options = default (TOptions);
      return false;
    }

    public virtual bool TryAdd(string? name, TOptions options)
    {
      ThrowHelper.ThrowIfNull((object) options, nameof (options));
      return this._cache.TryAdd(name ?? Microsoft.Extensions.Options.Options.DefaultName, new Lazy<TOptions>(options));
    }

    /// <summary>Try to remove an options instance.</summary>
    /// <param name="name">The name of the options instance.</param>
    /// <returns>Whether anything was removed.</returns>
    public virtual bool TryRemove(string? name)
    {
      return this._cache.TryRemove(name ?? Microsoft.Extensions.Options.Options.DefaultName, out Lazy<TOptions> _);
    }
  }
}

OptionsCache 不做详细分析

Q&A

  • 为什么前面都在说IOptionsSnapshot是Scope,IOptions是单例… , 是怎么知道他们对应的DI配置的?

    无论是调用 services.Configure<TOptions>(Configuration.GetSection("xxx"));方法,还是services.Configure<TOptions>(() => {//..});services.AddOptions<TOptions>("nameOptions").Configure(op => //... );方法,他始终都会调用到 services.AddOptions();方法。具体看一下这个方法:

    public static class OptionsServiceCollectionExtensions{
       //.....
      public static IServiceCollection AddOptions(this IServiceCollection services){
         ThrowHelper.ThrowIfNull((object) services, nameof (services));
         services.TryAdd(ServiceDescriptor.Singleton(typeof (IOptions<>), typeof (UnnamedOptionsManager<>)));
         services.TryAdd(ServiceDescriptor.Scoped(typeof (IOptionsSnapshot<>), typeof (OptionsManager<>)));
         services.TryAdd(ServiceDescriptor.Singleton(typeof (IOptionsMonitor<>), typeof (OptionsMonitor<>)));
         services.TryAdd(ServiceDescriptor.Transient(typeof (IOptionsFactory<>), typeof (OptionsFactory<>)));
         services.TryAdd(ServiceDescriptor.Singleton(typeof (IOptionsMonitorCache<>), typeof (OptionsCache<>)));
         return services;
       }
     //.....
    }
    

    在这里就可以清楚的看见为什么说IOptionsSnapshot是Scope,IOptions是单例了。

    从以上源码可以看出:

    • IOptions的实现类是UnnamedOptionsManager,对应DI配置是单例
    • IOptionsSnapshot的实现类是OptionsManager,对应DI配置是Scope
    • IOptionsMonitor的实现类是OptionsMonitor,对应DI配置是单例
    • IOptionsFactory的实现类是OptionsFactory,对应DI配置是瞬时
    • IOptionsMonitorCache的实现类是OptionsCache,对应DI配置是单例
  • UnnamedOptionsManager,OptionsManager,IOptionsMonitor中都有对IOptionsFactory对注入,但是IOptionsFactory的作用域是瞬时,而其他类的作用域是单例 和 Scope , 小范围的DI注册到大范围中,如果小范围的service释放了,不会对大范围产生影响吗?(IOptionsMonitor单例中注入,IOptionsFactory瞬时,IOptionsFactory释放,再此调用IOptionsMonitor中的factory不会产生异常吗?)–未想解决

总结

extra

C# Lazy(延迟初始化)

简介

通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率,同时Lazy是天生线程安全的

用法

  • 构造时使用默认的初始化方式

    namespace LazyTest
    {
        class LazyDemo
        {
            static void Main()
            {
                Lazy<Data> lazyData = new Lazy<Data>();
                Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
                lazyData.Value.Print();//此处访问时才会将Data真正的初始化
                Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
                Console.ReadKey();
            }
        }
        class Data
        {
            public Data() 
            {
                Console.WriteLine("Data::.ctor->Initialized");
            }
    
            public void Print()
            {
                Console.WriteLine("Data::Print->println");
            }
        }
    }
    
    

    执行结果:

    Main->is lazyData Initialized? value = False
    Data::.ctor->Initialized
    Data::Print->println
    Main->is lazyData Initialized? value = True
    
  • 构造时使用指定的委托初始化

    namespace LazyTest
    {
        class LazyDemo
        {
            static void Main()
            {
                //指定委托来初始化Data
                Lazy<Data> lazyData = new Lazy<Data>(
                    () =>
                    {
                        Console.WriteLine("Main->lazyData will be Initialized!");
                        return new Data("Test");
                    });
                Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
                lazyData.Value.Print();
                Console.WriteLine("Main->is lazyData Initialized? value = " + lazyData.IsValueCreated);
    
    
                Console.ReadKey();
            }
        }
    
        class Data
        {
            public string Name { get; private set; }
    
            public Data(string name)
            {
                Name = name;
                Console.WriteLine("Data::.ctor->Initialized,name = "+name);
            }
    
            public void Print()
            {
                Console.WriteLine("Data::Print->name = " + Name);
            }
        }
    }
    

    执行结果:

    Main->is lazyData Initialized? value = False
    Main->lazyData will be Initialized!
    Data::.ctor->Initialized,name = Test
    Data::Print->name = Test
    Main->is lazyData Initialized? value = True
    

Lazy.Value的使用

Lazy对象创建后,并不会立即创建对应的对象,只有在变量的Value属性被首次访问时才会真正的创建,同时会将其缓存到Value中,以便将来访问。

Value属性是只读的,也就意味着如果Value存储了引用类型,将无法为其分配新对象,只可以更改此对象公共的属性或者字段等,如果Value存储的是值类型,那么就不能修改其值了,只能通过再次调用变量的函数使用新的参数来创建新的变量

在Lazy对象创建后,在首次访问变量的Value属性前,

实现延迟属性

class Customer
{
    private Lazy<Orders> _orders;

    public string CustomerID {get; private set;}

    public Customer(string id)
    {
        CustomerID = id;
        _orders = new Lazy<Orders>(() =>
        {
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            return _orders.Value;
        }
    }
}

在Lazy.Value的使用中可以得知:Value的属性是只读的,所以示例中只提供了Get的访问器,并未提供Set的访问器。

默认DI泛型注册小点

demo代码

public interface ITest<TOptions>
{
    string GetValue();
}
public class Test<TOptions> : ITest<TOptions>
{
    public string GetValue()
    {
        return typeof(TOptions).ToString() + "1";
    }
}
class Program
{
    public static void Main(string[] args)
    {
        IServiceCollection services = new ServiceCollection();
        services.Add(ServiceDescriptor.Singleton(typeof(ITest<>), typeof(Test<>)));
        var serviceFactory = new DefaultServiceProviderFactory();
        IServiceProvider serviceProvider = serviceFactory.CreateServiceProvider(services);

        var demo1 = serviceProvider.GetService<ITest<DemoOne>>();
        var demo2 = serviceProvider.GetService<ITest<DemoTwo>>();
        

        Console.WriteLine(demo1.GetHashCode());
        Console.WriteLine(demo2.GetHashCode());
        
        Console.WriteLine(demo1.GetValue());
        Console.WriteLine(typeof(ITest<>).GetGenericTypeDefinition());
        Console.WriteLine(typeof(ITest<DemoOne>).GetGenericTypeDefinition());
    }
    class DemoOne
    {
        public string Name { get; set; }
    }

    class DemoTwo
    {
        public string Age { get; set; }
    }
}

问题描述 and Solve

已知:

  • 在servicecollection中注册了ServiceDescriptor.Singleton(typeof(ITest<>), typeof(Test<>))

  • ServiceDescriptorServiceTypeITest<TOptions>

  • ServiceDescriptor_implementationTypeTest<TOptions>

  • ServiceDescriptorServiceKeynull

  • 调用serviceProvider.GetService<ITest<DemoOne>>();必然会先创建ITest<DemoOne>ServiceDescriptor:

  • ServiceDescriptorServiceTypeITest<DemoOne>

  • ServiceDescriptor_implementationTypeTest<DemoOne>

  • ServiceDescriptorServiceKeynull

  • 调用serviceProvider.GetService<ITest<DemoOne>>();必然会先创建ITest<DemoTwo>ServiceDescriptor:

  • ServiceDescriptorServiceTypeITest<DemoTwo>

  • ServiceDescriptor_implementationTypeTest<DemoTwo>

  • ServiceDescriptorServiceKeynull

  • 在初始化serviceProvider的时候,会创建调用站点工厂CallSiteFactory,CallSiteFactory的构造函数中会将servicecollection中的所有ServiceDescripto注册到private readonly Dictionary<ServiceIdentifier, ServiceDescriptorCacheItem> _descriptorLookup 【typeof(ITest<>)的ServiceDescriptor注册到_descriptorLookup中】

//CallSiteFactory构造函数
public CallSiteFactory(ICollection<ServiceDescriptor> descriptors){
   _stackGuard = new StackGuard();
   _descriptors = new ServiceDescriptor[descriptors.Count];
   descriptors.CopyTo(_descriptors, 0);
   Populate();
 }
//Populate方法:
private void Populate(){
  foreach (ServiceDescriptor descriptor in _descriptors){
		//......
    var cacheKey = ServiceIdentifier.FromDescriptor(descriptor);
    _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem cacheItem);
    _descriptorLookup[cacheKey] = cacheItem.Add(descriptor);
  }
}

问题:

ITest<DemoOne>ITest<DemoOne>创建的ServiceIdentifier 与 ITest<TOptions>创建的ServiceIdentifier都不是同一内容的,但是在创建服务调用站点(ServiceCallSite)的时候讲道理会先从_descriptorLookup中判断是否有对应ServiceIdentifier的服务,再去创建,但是【ITest<DemoOne>ITest<DemoOne> 】对 ITest<TOptions>都不是同一个serviceType,那【ITest<DemoOne>ITest<DemoOne> 】是如何拿到对应的 Test<TOptions>实现类的?

查源码可得:

  • 往 _descriptorLookup数组中添加元素的源码位置 【Populate方法】
  1. 先介绍一下关于descriptor的构建先:

    //关于下面这两种添加service,构建descriptor的区别
    a: services.AddSingleton<Test>();
    b: services.AddSingleton<ITest,Test>();
    对于a的方式,descriptor的 serviceType 和 ImplementationType 的值就都会为 Test
    对于b的方式,descriptor的 serviceType 为ITest,ImplementationType为Test
    
  2. 再仔细看一下populate方法,因为到后面你取不取得到service全靠这里的缓存了_descriptorLookup的缓存,而Populate就规定了什么东西是可以加入到_descriptorLookup中的

     private void Populate(){
       foreach (ServiceDescriptor descriptor in _descriptors){
         //取出ServiceDescriptor中的ServiceType
         Type serviceType = descriptor.ServiceType;
         //判断 serviceType是否是泛型定义【什么是泛型定义?typeof(ITest<>)这种就是泛型定义,typeof(ITest<DemoOne>)这种就不是泛型定义,就无法进入if中,判断走到else
         if (serviceType.IsGenericTypeDefinition){
           //取出ServiceDescriptor中的implementationType
           Type? implementationType = descriptor.GetImplementationType();
           //如果 implementationType 为空或不为泛型定义(Test<>)则抛出异常
           if (implementationType == null || !implementationType.IsGenericTypeDefinition){
             throw new ArgumentException //...
           }
           //如果implementationType不是class类型就抛出异常
           if (implementationType.IsAbstract || implementationType.IsInterface){
             throw new ArgumentException //...
           }
           //下部分就是判断 serviceType 中的泛型定义的泛型参数 需要和 implementationType的泛型定义的泛型参数的数量是否保存一致
           Type[] serviceTypeGenericArguments = serviceType.GetGenericArguments();
           Type[] implementationTypeGenericArguments = implementationType.GetGenericArguments();
           if (serviceTypeGenericArguments.Length != implementationTypeGenericArguments.Length){
             throw new ArgumentException //...
           }
           //...
         }
         //取出ServiceDescriptor中的ServiceType不是泛型定义则会进入这,如:ITest<DemoOne>,DemoOne等
         else if (descriptor.TryGetImplementationType(out Type? implementationType)){
           //判断ImplementationType如果是泛型定义,接口,抽象类则抛出异常
           if (implementationType.IsGenericTypeDefinition 
               ||implementationType.IsAbstract 
               ||implementationType.IsInterface){
             throw new ArgumentException //...
           }
         }
         var cacheKey = ServiceIdentifier.FromDescriptor(descriptor)
         _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem cacheItem);
         _descriptorLookup[cacheKey] = cacheItem.Add(descriptor);
       }
     }
    

    从以上代码可以得:

    • 情况一:serviceType 和 ImplementationType 都是通过手动传入的

      • 那serviceType必须是泛型定义,且ImplementationType也必须为泛型定义,且两个的泛型参数数量必须相同。
    • 情况二:只传入serviceType【构造器会把serviceType赋值到ImplementationType中的】

      • 那传入的serviceType必须是可实例化的Type
  • serviceProvider.GetService<ITest<DemoOne>>()是如何获取到services.Add(ServiceDescriptor.Singleton(typeof(ITest<>), typeof(Test<>)));中的Test<>实现类

  • 可以直接看到TryCreateOpenGeneric()方法中了,泛型类型就是在这进行创建的

    private ServiceCallSite? TryCreateOpenGeneric(
      ServiceIdentifier serviceIdentifier, 
      CallSiteChain callSiteChain){
      //判断 serviceIdentifier的servicetype 是否是可以实例化的泛型定义【ITest<DemoOne>这种就是可以实例化的泛型定义,ITest<TOptions>这种就是不可实例化的泛型定义】
      if (serviceIdentifier.IsConstructedGenericType){
       //获取ITest<DemoOne>的泛型定义ITest<TOptions>
       var genericIdentifier = serviceIdentifier.GetGenericTypeDefinition();
        //从_descriptorLookup中获取对应 ServiceIdentifier的ServiceDescriptorCacheItem,ServiceIdentifier中的serviceType 为 ITest<TOptions>而不是 ITest<DemoOne>自然就可以找到对应的 ServiceDescriptorCacheItem了
       if (_descriptorLookup.TryGetValue(genericIdentifier, out ServiceDescriptorCacheItem descriptor)){
         return TryCreateOpenGeneric(descriptor.Last, 
                                     serviceIdentifier, 
                                     callSiteChain, 
                                     DefaultSlot, true);
       }
        //首先这里不需要看,因为ServiceKey==null
        if (serviceIdentifier.ServiceKey != null){
          //...
        }
        return null;
    }
    
  • 从以上可得:

  • 加载serviceProvider的CallSiteFactory的_descriptorLookup的key值(ServiceIdentifier)的serviceKey为ITest<TOptions>泛型定义

  • 通过serviceProvider.GetService取service时构造的ServiceIdentifier的serviceKey为serviceProvider.GetService<ITest<DemoOne>>()ITest<DemoOne>的泛型定义ITest<DemoOne>,所以serviceProvider.GetService<ITest<DemoOne>>()是取得到对应typeof(ITest<>)的实现类typeof(Test<>)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值