细聊.NET6 ConfigurationManager的实现
前言
友情提示:建議閱讀本文之前先了解下.Net Core配置體系相關(guān),也可以參考本人之前的文章《.Net Core Configuration源碼探究?[1]》然后對(duì).Net Core的Configuration體系有一定的了解,使得理解起來(lái)更清晰。
????在.Net6中關(guān)于配置相關(guān)多出一個(gè)關(guān)于配置相關(guān)的類ConfigurationManager,如果大概了解過(guò)Minimal API中的WebApplicationBuilder類相信你肯定發(fā)現(xiàn)了,在Minimal API中的配置相關(guān)屬性Configuration正是ConfigurationManager的對(duì)象。ConfigurationManager本身并沒(méi)有引入新的技術(shù),也不是一個(gè)體系,只是在原來(lái)的基礎(chǔ)上進(jìn)行了進(jìn)一步的封裝,使得配置體系有了一個(gè)新的外觀操作,暫且可以理解為新瓶裝舊酒。本文我們就來(lái)了解下ConfigurationManager類,來(lái)看下微軟為何在.Net6中會(huì)引入這么一個(gè)新的操作。
使用方式
關(guān)于.Net6中ConfigurationManager的使用方式,我們先通過(guò)簡(jiǎn)單的示例演示一下
ConfigurationManager configurationManager = new(); configurationManager.AddJsonFile("appsettings.json",true,reloadOnChange:true); string serviceName = configurationManager["ServiceName"]; Console.WriteLine(serviceName);當(dāng)然,關(guān)于獲取值得其他方式。比如GetSection、GetChildren相關(guān)方法還是可以繼續(xù)使用的,或者使用Binder擴(kuò)展包相關(guān)的Get<string>()、GetValue<NacosOptions>("nacos")類似的方法也照樣可以使用。那它和之前的.Net Core上的配置使用起來(lái)有什么不一樣呢,我們看一下之前配置相關(guān)的使用方式,如下所示
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName);這里需要注意的是,如果你是使用ConfigurationManager或者是IConfiguration封裝的Helper類相關(guān),并沒(méi)有通過(guò)框架體系默認(rèn)注入的時(shí)候,一定要注意將其設(shè)置為單例模式。其實(shí)這個(gè)很好理解,先不說(shuō)每次用的時(shí)候都去實(shí)例化帶來(lái)的內(nèi)存CPU啥的三高問(wèn)題。讀取配置文件本質(zhì)不就是把數(shù)據(jù)讀到內(nèi)存中嗎?內(nèi)存中有一份緩存這就好了,每次都去重新實(shí)例去讀本身就是一種不規(guī)范的方式。許多時(shí)候如果你實(shí)在不知道該定義成什么樣的生命周期,可以參考微軟的實(shí)現(xiàn)方式,以ConfigurationManager為例,我們可以參考WebApplicationBuilder類中對(duì)ConfigurationManager注冊(cè)的生命周期[點(diǎn)擊查看源碼👈[2]]
public ConfigurationManager Configuration { get; } = new(); //這里注冊(cè)為了單例模式 Services.AddSingleton<IConfiguration>(_ => Configuration);通過(guò)上面我們演示的示例可以看出在ConfigurationManager的時(shí)候注冊(cè)配置和讀取配置相關(guān)都只是使用了這一個(gè)類。而在之前的配置體系中,注冊(cè)配置需要使用IConfigurationBuilder,然后通過(guò)Build方法得到IConfiguration實(shí)例,然后讀取是通過(guò)IConfiguration實(shí)例進(jìn)行的。本身操作配置的時(shí)候IConfigurationBuilder和IConfiguration是滿足單一職責(zé)原則沒(méi)問(wèn)題,像讀取配置這種基礎(chǔ)操作,應(yīng)該是越簡(jiǎn)單越好,所以微軟才進(jìn)一步封裝了ConfigurationManager來(lái)簡(jiǎn)化配置相關(guān)的操作。
在.Net6中微軟并沒(méi)有放棄IConfigurationBuilder和IConfiguration,因?yàn)檫@是操作配置文件的基礎(chǔ)類,微軟只是借助了它們兩個(gè)在上面做了進(jìn)一層封裝而已,這個(gè)是需要我們了解的。
源碼探究
上面我們了解了新的ConfigurationManager的使用方式,這里其實(shí)我們有疑問(wèn)了,為什么ConfigurationManager可以進(jìn)行注冊(cè)和讀取操作。上面我提到過(guò)ConfigurationManager本身就是新瓶裝舊酒,而且它只是針對(duì)原有的配置體系做了一個(gè)新的外觀,接下來(lái)哦我們就從源碼入手,看一下它的實(shí)現(xiàn)方式。
定義入手
首先來(lái)看一下ConfigurationManager的的定義,如下所示[點(diǎn)擊查看源碼👈[3]]
public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable { }其實(shí)只看它的定義就可以解答我們心中的大部分疑惑了,之所以ConfigurationManager能夠滿足IConfigurationBuilder和IConfigurationRoot這兩個(gè)操作的功能是因?yàn)樗旧砭褪菍?shí)現(xiàn)了這兩個(gè)接口,集它們的功能于一身了,IConfigurationRoot接口本身就集成自IConfiguration接口。因此如果給ConfigurationManager換個(gè)馬甲的話你就會(huì)發(fā)現(xiàn)還是原來(lái)的配方還是原來(lái)的味道
ConfigurationManager configurationManager = new(); IConfigurationBuilder configurationBuilder = configurationManager.AddJsonFile("appsettings.json", true, reloadOnChange: true); //盡管放心的調(diào)用Build完全不影響啥 IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName);這種寫法只是為了更好的看清它的本質(zhì),如果真實(shí)操作這么寫,確實(shí)有點(diǎn)畫蛇添足了,因?yàn)镃onfigurationManager本身就是為了簡(jiǎn)化我們的操作。
認(rèn)識(shí)IConfigurationBuilder和IConfiguration
通過(guò)上面我們了解到ConfigurationManager可以直接注冊(cè)過(guò)配置文件就可以直接去操作配置文件里的內(nèi)容,這一步是肯定通過(guò)轉(zhuǎn)換得到的,畢竟之前的方式我們是通過(guò)IConfigurationBuilder的Build操作得到的IConfiguration的實(shí)例,那么我們就先來(lái)看下原始的方式是如何實(shí)現(xiàn)的。這里需要從IConfigurationBuilder的默認(rèn)實(shí)現(xiàn)類ConfigurationBuilder說(shuō)起,它的實(shí)現(xiàn)很簡(jiǎn)單[點(diǎn)擊查看源碼👈[4]]
public class ConfigurationBuilder : IConfigurationBuilder {/// <summary>/// 添加的數(shù)據(jù)源被存放到了這里/// </summary>public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();/// <summary>/// 添加IConfigurationSource數(shù)據(jù)源/// </summary>/// <returns></returns>public IConfigurationBuilder Add(IConfigurationSource source){if (source == null){throw new ArgumentNullException(nameof(source));}Sources.Add(source);return this;}public IConfigurationRoot Build(){//獲取所有添加的IConfigurationSource里的IConfigurationProvidervar providers = new List<IConfigurationProvider>();foreach (var source in Sources){var provider = source.Build(this);providers.Add(provider);}//用providers去實(shí)例化ConfigurationRootreturn new ConfigurationRoot(providers);} }這里我們來(lái)解釋一下,其實(shí)我們注冊(cè)配置相關(guān)的時(shí)候比如AddJsonFile()、AddEnvironmentVariables()、AddInMemoryCollection()等等它們其實(shí)都是擴(kuò)展方法,本質(zhì)就是添加IConfigurationSource實(shí)例,而IConfigurationBuilder的Build本質(zhì)操作其實(shí)就是在IConfigurationSource集合中得到IConfigurationProvider集合,因真正從配置讀取到的數(shù)據(jù)都是包含在IConfigurationProvider實(shí)例中的,ConfigurationRoot通過(guò)一系列的封裝,讓我們可以更便捷的得到配置里相關(guān)的信息。這就是ConfigurationBuilder的工作方式,也是配置體系的核心原理。我們既然知道了添加配置的本質(zhì)其實(shí)就是IConfigurationBuilder.Add(IConfigurationSource source)那么我就來(lái)看一下ConfigurationManager是如何實(shí)現(xiàn)這一步的。我們知道ConfigurationManager實(shí)現(xiàn)了IConfigurationBuilder接口,所以必然重寫了IConfigurationBuilder的Add方法,找到源碼位置[點(diǎn)擊查看源碼👈[5]]
private readonly ConfigurationSources _sources = new ConfigurationSources(this); ; IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) {_sources.Add(source ?? throw new ArgumentNullException(nameof(source)));return this; }這里返回了this也就是當(dāng)前ConfigurationManager實(shí)例是為了可以進(jìn)行鏈?zhǔn)骄幊?#xff0c;ConfigurationSources這個(gè)類是個(gè)新物種,原來(lái)的類叫ConfigurationSource,這里多了個(gè)s表明了這是一個(gè)集合類,我們就來(lái)看看它是個(gè)啥操作,找到源碼位置[點(diǎn)擊查看源碼👈[6]]
/// <summary> /// 本身是一個(gè)IConfigurationSource集合 /// </summary> private class ConfigurationSources : IList<IConfigurationSource> {private readonly List<IConfigurationSource> _sources = new();private readonly ConfigurationManager _config;/// <summary>/// 因?yàn)槭荂onfigurationManager的內(nèi)部類所以傳遞了當(dāng)前ConfigurationManager實(shí)例/// </summary>/// <param name="config"></param>public ConfigurationSources(ConfigurationManager config){_config = config;}/// <summary>/// 根據(jù)索引獲取其中一個(gè)IConfigurationSource實(shí)例/// </summary>/// <returns></returns>public IConfigurationSource this[int index]{get => _sources[index];set{_sources[index] = value;_config.ReloadSources();}}public int Count => _sources.Count;public bool IsReadOnly => false;/// <summary>/// 這是重點(diǎn)添加配置源/// </summary>/// <param name="source"></param>public void Add(IConfigurationSource source){//給自己的IConfigurationSource集合添加_sources.Add(source);//調(diào)用了ConfigurationManager的AddSource方法_config.AddSource(source);}/// <summary>/// 實(shí)現(xiàn)IList清除操作/// </summary>public void Clear(){_sources.Clear();//這里可以看到ConfigurationManager的ReloadSources方法很重要//通過(guò)名字可以看出是刷新配置數(shù)據(jù)用的_config.ReloadSources();}public void Insert(int index, IConfigurationSource source){_sources.Insert(index, source);_config.ReloadSources();}public bool Remove(IConfigurationSource source){var removed = _sources.Remove(source);_config.ReloadSources();return removed;}public void RemoveAt(int index){_sources.RemoveAt(index);_config.ReloadSources();}//這里省略了實(shí)現(xiàn)了實(shí)現(xiàn)IList接口的其他操作//ConfigurationSources本身就是IList<IConfigurationSource> }正如我們看到的那樣ConfigurationSources本身就是一個(gè)IConfigurationSource的集合,在新的.Net體系中微軟喜歡把集合相關(guān)的操作封裝一個(gè)Collection類,這樣的好處就是讓大家能更清晰的了解它是功能實(shí)現(xiàn)類,而不在用一個(gè)數(shù)據(jù)結(jié)構(gòu)的眼光去看待。通過(guò)源碼我們還看到了Add方法里還調(diào)用了ConfigurationManager的AddSource方法,這究竟是一個(gè)什么操作我們來(lái)看下[點(diǎn)擊查看源碼👈[7]]
private readonly object _providerLock = new(); private readonly List<IConfigurationProvider> _providers = new(); private readonly List<IDisposable> _changeTokenRegistrations = new(); private void AddSource(IConfigurationSource source) {lock (_providerLock){//在IConfigurationSource中得到IConfigurationProvider實(shí)例var provider = source.Build(this);//添加到_providers集合中//我們提到過(guò)從配置源得到的配置都是通過(guò)IConfigurationProvider得到的_providers.Add(provider);//IConfigurationProvider的Load方法是從配置源中得到配置數(shù)據(jù)加載到程序內(nèi)存中provider.Load();//注冊(cè)更改令牌操作,使得配置可以進(jìn)行動(dòng)態(tài)刷新加載_changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged()));}//添加新的配置源要刷新令牌操作RaiseChanged(); }private ConfigurationReloadToken _changeToken = new(); private void RaiseChanged() {//每次對(duì)配置源進(jìn)行更改操作需要得到新的更改令牌實(shí)例,用于可重復(fù)通知配置變更相關(guān)var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());previousToken.OnReload(); }從上面的ConfigurationSources方法里我們可以看到動(dòng)態(tài)的針對(duì)ConfigurationSources里的ConfigurationSource進(jìn)行更改會(huì)每次都調(diào)用ReloadSources方法,我們來(lái)看一下它的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈[8]]
private readonly object _providerLock = new(); private void ReloadSources() {lock (_providerLock){//釋放原有操作DisposeRegistrationsAndProvidersUnsynchronized();//清除更改令牌_changeTokenRegistrations.Clear();//清除_providers_providers.Clear();//重新加載_providersforeach (var source in _sources){_providers.Add(source.Build(this));}//重新加載數(shù)據(jù)添加通知令牌foreach (var p in _providers){p.Load();_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));}}RaiseChanged(); }這個(gè)方法幾乎是重新清除了原來(lái)的操作,然后完全的重新加載一遍數(shù)據(jù),理論上來(lái)說(shuō)是一個(gè)低性能的操作,不建議頻繁使用。還有因?yàn)镃onfigurationManager實(shí)現(xiàn)了IConfigurationBuilder接口所以也必然實(shí)現(xiàn)了它的Build方法少不了,看一下它的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈[9]]
IConfigurationRoot IConfigurationBuilder.Build() => this;這波操作真的很真的很騷氣,我即是IConfigurationRoot我也是IConfigurationBuilder,反正操作都是我自己,所以這里你可勁的Build也不影響啥,反正得到的也都是一個(gè)ConfigurationManager實(shí)例。到了這里結(jié)合我們之前了解到的傳統(tǒng)的IConfigurationBuilder和IConfiguration關(guān)系,以及我們上面展示的展示的ConfigurationSources類的實(shí)現(xiàn)和ConfigurationManager的AddSource方法。其實(shí)我們可以發(fā)現(xiàn)我們上面展示的ConfigurationManager類的相關(guān)操作其實(shí)就是實(shí)現(xiàn)了之前ConfigurationBuilder類里的操作。其實(shí)這里微軟可以不用實(shí)現(xiàn)ConfigurationSources類完全基于ConfigurationBuilder也能實(shí)現(xiàn)一套,但是顯然微軟沒(méi)這么做,具體想法咱們不得而知,估計(jì)是只想以來(lái)抽象,而并不像以來(lái)原來(lái)的實(shí)現(xiàn)方式吧。
我們上面展示的這一部分的ConfigurationManager代碼,其實(shí)就是替代了原來(lái)的ConfigurationBuilder類的功能。
讀取操作
上面我們看到了在ConfigurationManager中關(guān)于以前ConfigurationManager類的實(shí)現(xiàn)。接下來(lái)我們看一下讀取相關(guān)的操作,即在這里ConfigurationManager成為了IConfiguration實(shí)例,所以我們先來(lái)看下IConfiguration接口的定義[點(diǎn)擊查看源碼👈[10]]
public interface IConfiguration {/// <summary>/// 通過(guò)配置名稱獲取值/// </summary>/// <returns></returns>string this[string key] { get; set; }/// <summary>/// 獲取一個(gè)配置節(jié)點(diǎn)/// </summary>/// <returns></returns>IConfigurationSection GetSection(string key);/// <summary>/// 獲取所有子節(jié)點(diǎn)/// </summary>/// <returns></returns>IEnumerable<IConfigurationSection> GetChildren();/// <summary>/// 刷新數(shù)據(jù)通知/// </summary>/// <returns></returns>IChangeToken GetReloadToken(); }通過(guò)代碼我們看到了IConfiguration的定義,也就是在ConfigurationManager類中必然也實(shí)現(xiàn)也這幾個(gè)操作,首先便是通過(guò)索引器直接根據(jù)配置的名稱獲取值得操作[點(diǎn)擊查看源碼👈[11]]
private readonly object _providerLock = new(); private readonly List<IConfigurationProvider> _providers = new(); /// <summary> /// 可讀可寫的操作 /// </summary> /// <returns></returns> public string this[string key] {get{lock (_providerLock){//通過(guò)在IConfigurationProvider集合中獲取配置值return ConfigurationRoot.GetConfiguration(_providers, key);}}set{lock (_providerLock){//也可以把值放到IConfigurationProvider集合中ConfigurationRoot.SetConfiguration(_providers, key, value);}} }其中_providers中的值是我們?cè)贏ddSource方法中添加進(jìn)來(lái)的,這里的本質(zhì)其實(shí)還是針對(duì)ConfigurationRoot做了封裝。ConfigurationRoot實(shí)現(xiàn)了IConfigurationRoot接口,IConfigurationRoot實(shí)現(xiàn)了IConfiguration接口。而ConfigurationRoot的GetConfiguration方法和SetConfiguration是最直觀體現(xiàn)ConfigurationRoot本質(zhì)就是IConfigurationProvider包裝的證據(jù)。我們來(lái)看一下ConfigurationRoot這兩個(gè)方法的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈[12]]
internal static string GetConfiguration(IList<IConfigurationProvider> providers, string key) {//倒序遍歷providers,因?yàn)镃onfiguration采用的后來(lái)者居上的方式,即后注冊(cè)的Key會(huì)覆蓋先前注冊(cè)的Keyfor (int i = providers.Count - 1; i >= 0; i--){IConfigurationProvider provider = providers[i];//如果找到Key的值就直接返回if (provider.TryGet(key, out string value)){return value;}}return null; }internal static void SetConfiguration(IList<IConfigurationProvider> providers, string key, string value) {if (providers.Count == 0){throw new InvalidOperationException("");}//給每個(gè)provider都Set這個(gè)鍵值,雖然浪費(fèi)了一部分內(nèi)存,但是可以最快的獲取foreach (IConfigurationProvider provider in providers){provider.Set(key, value);} }關(guān)于GetSection的方法實(shí)現(xiàn),本質(zhì)上是返回ConfigurationSection實(shí)例,ConfigurationSection本身也是實(shí)現(xiàn)了IConfiguration接口,所有關(guān)于配置獲取的操作出口都是面向IConfiguration的。
public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key);GetChildren方法是獲取配置的所有子節(jié)點(diǎn)的操作,本質(zhì)是返回IConfigurationSection的集合,實(shí)現(xiàn)方式如如下
private readonly object _providerLock = new(); public IEnumerable<IConfigurationSection> GetChildren() {lock (_providerLock){//調(diào)用了GetChildrenImplementation方法return this.GetChildrenImplementation(null).ToList();} }這里調(diào)用了GetChildrenImplementation方法,而GetChildrenImplementation是一個(gè)擴(kuò)展方法,我們來(lái)看一下它的實(shí)現(xiàn)[點(diǎn)擊查看源碼👈[13]]
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path) {//在當(dāng)前ConfigurationManager實(shí)例中獲取到所有的IConfigurationProvider實(shí)例//然后包裝成IConfigurationSection集合return root.Providers.Aggregate(Enumerable.Empty<string>(),(seed, source) => source.GetChildKeys(seed, path)).Distinct(StringComparer.OrdinalIgnoreCase).Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); }通過(guò)這段代碼再次應(yīng)驗(yàn)了那句話所有獲取配置數(shù)據(jù)都是面向IConfiguration接口的,數(shù)據(jù)本質(zhì)都是來(lái)自于IConfigurationProvider讀取配置源中的數(shù)據(jù)。
ConfigurationBuilderProperties
在ConfigurationManager中還包含了一個(gè)Properties屬性,這個(gè)屬性本質(zhì)來(lái)源于IConfigurationBuilder。在IConfigurationBuilder中它和IConfigurationSource是平行關(guān)系,IConfigurationSource用于在配置源中獲取數(shù)據(jù),而Properties是在內(nèi)存中獲取數(shù)據(jù),本質(zhì)是一個(gè)字典
private readonly ConfigurationBuilderProperties _properties = new ConfigurationBuilderProperties(this); IDictionary<string, object> IConfigurationBuilder.Properties => _properties;這里咱們就不細(xì)說(shuō)這個(gè)具體實(shí)現(xiàn)了,我們知道它本質(zhì)是字典,然后操作都是純內(nèi)存的操作即可,來(lái)看一下它的定義[點(diǎn)擊查看源碼👈[14]]
private class ConfigurationBuilderProperties : IDictionary<string, object> { }基本上許多緩存機(jī)制即內(nèi)存操作都是基于字典做的一部分實(shí)現(xiàn),所以大家對(duì)這個(gè)實(shí)現(xiàn)的方式有一定的認(rèn)識(shí)即可,即使在配置體系的核心操作ConfigurationProvider中讀取的配置數(shù)據(jù)也是存放在字典中的。這個(gè)可以去ConfigurationProvider類中自行了解一下[點(diǎn)擊查看源碼👈[15]]
protected IDictionary<string, string> Data { get; set; } protected ConfigurationProvider() {Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); }總結(jié)
????通過(guò)本文我們了解到了.Net6配置體系中的新成員ConfigurationManager,它是一個(gè)新內(nèi)容但不是一個(gè)新技術(shù),因?yàn)樗窃谠械呐渲皿w系中封裝了一個(gè)新的外觀,以簡(jiǎn)化原來(lái)對(duì)配置相關(guān)的操作。原來(lái)對(duì)配置的操作需要涉及IConfigurationBuilder和IConfiguration兩個(gè)抽象操作,而新的ConfigurationManager只需要一個(gè)類,其本質(zhì)是因?yàn)镃onfigurationManage同時(shí)實(shí)現(xiàn)了IConfigurationBuilder和IConfiguration接口,擁有了他們兩個(gè)體系的能力。整體來(lái)說(shuō)重寫了IConfigurationBuilder的實(shí)現(xiàn)為主,而讀取操作主要還是借助原來(lái)的ConfigurationRoot對(duì)節(jié)點(diǎn)數(shù)據(jù)的讀取操作。
References
[1]?.Net Core Configuration源碼探究 :?https://www.cnblogs.com/wucy/p/13172284.html
[2]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.1/src/DefaultBuilder/src/WebApplicationBuilder.cs#L103
[3]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L17
[4]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationBuilder.cs
[5]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L97
[6]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L181
[7]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L127
[8]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L142
[9]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L103
[10]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/IConfiguration.cs
[11]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L42
[12]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationRoot.cs#L114
[13]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs#L21
[14]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationManager.cs#L262
[15]?點(diǎn)擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/Microsoft.Extensions.Configuration/src/ConfigurationProvider.cs#L30
總結(jié)
以上是生活随笔為你收集整理的细聊.NET6 ConfigurationManager的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: WPF GridControl控件的用法
- 下一篇: .NET6之MiniAPI(五):选项