说明
个人学习记录,可能有理解偏差,有误地方,望大佬可指正
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]
获取
- IConfiguration 中
-
我们常见的
builder.Configuration.AddJsonFile
他到底完成的是一件什么样子的事- 实际上应该就是向 IConfigurationBuilder中的
IList<IConfigurationSource> Sources
加入 jsonConfigurationSource ,在调用IConfigurationBuilder#build()
方法的时候就会创建对应的IConfigurationProvider
- 实际上应该就是向 IConfigurationBuilder中的
-
Configuration整一个东西的整体样貌
Option
简介
关于
IOption
:
- IOption被称为
选项模型
,它是在.netcore中引入的。它的作用是给我们的应用程序提供配置,这个配置就是一个个的对象模型。关于“依赖注入”和“选项模型”:
- “选项模型”本身是构筑在“依赖注入”框架上的,所以我们要想使用“选项模型”就必须先引入“依赖注入”框架。
关于“配置”和“选项模型”:
IConfiguration
和IOption
在.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<>))
ServiceDescriptor
的ServiceType
为ITest<TOptions>
ServiceDescriptor
的_implementationType
为Test<TOptions>
ServiceDescriptor
的ServiceKey
为null
调用
serviceProvider.GetService<ITest<DemoOne>>();
必然会先创建ITest<DemoOne>
的ServiceDescriptor
:
ServiceDescriptor
的ServiceType
为ITest<DemoOne>
ServiceDescriptor
的_implementationType
为Test<DemoOne>
ServiceDescriptor
的ServiceKey
为null
调用
serviceProvider.GetService<ITest<DemoOne>>();
必然会先创建ITest<DemoTwo>
的ServiceDescriptor
:
ServiceDescriptor
的ServiceType
为ITest<DemoTwo>
ServiceDescriptor
的_implementationType
为Test<DemoTwo>
ServiceDescriptor
的ServiceKey
为null
在初始化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方法】
先介绍一下关于descriptor的构建先:
//关于下面这两种添加service,构建descriptor的区别 a: services.AddSingleton<Test>(); b: services.AddSingleton<ITest,Test>(); 对于a的方式,descriptor的 serviceType 和 ImplementationType 的值就都会为 Test 对于b的方式,descriptor的 serviceType 为ITest,ImplementationType为Test
再仔细看一下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<>)