.NET8 依賴注入
依賴注入(Dependency Injection,簡稱DI)是一種設(shè)計模式,用于解耦組件(服務(wù))之間的依賴關(guān)系。它通過將依賴關(guān)系的創(chuàng)建和管理交給外部容器來實現(xiàn),而不是在組件(服務(wù))內(nèi)部直接創(chuàng)建依賴對象。
? 咱就是通過 IServiceCollection 和 IServiceProvider 來實現(xiàn)的,他們直接被收入到了runtime libraries,在整個.NET平臺下通用!
1.1 ServiceCollection
? IServiceCollection 本質(zhì)是一個 ServiceDescriptor 而 ServiceDescriptor 則是用于描述服務(wù)類型,實現(xiàn)和生命周期
public interface IServiceCollection :
IList<ServiceDescriptor>,
ICollection<ServiceDescriptor>,
IEnumerable<ServiceDescriptor>,
IEnumerable;? 官方提供一些列拓展幫助我們向服務(wù)容器中添加服務(wù)描述,具體在 ServiceCollectionServiceExtensions
builder.Services.AddTransient<StudentService>();
builder.Services.AddKeyedTransient<IStudentRepository, StudentRepository>("a");
builder.Services.AddKeyedTransient<IStudentRepository, StudentRepository2>("b");
builder.Services.AddTransient<TransientService>();
builder.Services.AddScoped<ScopeService>();
builder.Services.AddSingleton<SingletonService>();1.2 ServiceProvider
? IServiceProvider 定義了一個方法 GetService,幫助我們通過給定的服務(wù)類型,獲取其服務(wù)實例
public interface IServiceProvider
{
object? GetService(Type serviceType);
}? 下面是 GetService 的默認實現(xiàn)(如果不給定engine scope,則默認是root)
public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
? 也就是
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
// 獲取服務(wù)標識符對應(yīng)的服務(wù)訪問器
ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, _createServiceAccessor);
// 執(zhí)行解析時的hock
OnResolve(serviceAccessor.CallSite, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
// 通過服務(wù)訪問器提供的解析服務(wù),得到服務(wù)實例
object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));
return result;
}? 其中,服務(wù)標識符 ServiceIdentifier 其實就是包了一下服務(wù)類型,和服務(wù)Key(為了.NET8的鍵化服務(wù))
internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
}? 顯而易見的,我們的服務(wù)解析是由 serviceAccessor.RealizedService 提供,而創(chuàng)建服務(wù)訪問器 serviceAccessor 只有一個實現(xiàn) CreateServiceAccessor
private ServiceAccessor CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
{
// 通過 CallSiteFactory 獲取服務(wù)的調(diào)用點(CallSite),這是服務(wù)解析的一個表示形式。
ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());
// 如果調(diào)用站點不為空,則繼續(xù)構(gòu)建服務(wù)訪問器。
if (callSite != null)
{
DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceIdentifier.ServiceType, callSite);
// 觸發(fā)創(chuàng)建調(diào)用站點的相關(guān)事件。
OnCreate(callSite);
// 如果調(diào)用站點的緩存位置是根(Root),即表示這是一個單例服務(wù)。
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
// 直接拿緩存內(nèi)容
object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return new ServiceAccessor { CallSite = callSite, RealizedService = scope => value };
}
// 通過引擎解析
Func<ServiceProviderEngineScope, object?> realizedService = _engine.RealizeService(callSite);
return new ServiceAccessor { CallSite = callSite, RealizedService = realizedService };
}
// 如果調(diào)用點為空,則它的實現(xiàn)服務(wù)函數(shù)總是返回 null。
return new ServiceAccessor { CallSite = callSite, RealizedService = _ => null };
}1.2.1 ServiceProviderEngine
? ServiceProviderEngine 是服務(wù)商解析服務(wù)的執(zhí)行引擎,它在服務(wù)商被初始化時建立。有兩種引擎,分別是動態(tài)引擎和運行時引擎,在 NETFRAMEWORK || NETSTANDARD2_0 默認使用動態(tài)引擎。
private ServiceProviderEngine GetEngine()
{
ServiceProviderEngine engine;
#if NETFRAMEWORK || NETSTANDARD2_0
engine = CreateDynamicEngine();
#else
if (RuntimeFeature.IsDynamicCodeCompiled && !DisableDynamicEngine)
{
engine = CreateDynamicEngine();
}
else
{
// Don't try to compile Expressions/IL if they are going to get interpreted
engine = RuntimeServiceProviderEngine.Instance;
}
#endif
return engine;
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715
ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this);
}? 由于.NET Aot技術(shù)與dynamic技術(shù)沖突,因此Aot下只能使用運行時引擎,但動態(tài)引擎在大多情況下仍然是默認的。
動態(tài)引擎使用了emit技術(shù),這是一個動態(tài)編譯技術(shù),而aot的所有代碼都需要在部署前編譯好,因此運行時無法生成新的代碼。那運行時引擎主要使用反射,目標是在不犧牲太多性能的情況下,提供一個在aot環(huán)境中可行的解決方案。
? 我們展開動態(tài)引擎來看看它是如何解析服務(wù)的。
public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
{
// 定義一個局部變量來跟蹤委托被調(diào)用的次數(shù)
int callCount = 0;
return scope =>
{
// 當委托被調(diào)用時,先使用CallSiteRuntimeResolver.Instance.Resolve方法來解析服務(wù)。這是一個同步操作,確保在編譯優(yōu)化之前,服務(wù)可以被正常解析。
var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
// 委托第二次被調(diào)用,通過UnsafeQueueUserWorkItem在后臺線程上啟動編譯優(yōu)化
if (Interlocked.Increment(ref callCount) == 2)
{
// 將一個工作項排隊到線程池,但不捕獲當前的執(zhí)行上下文。
_ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
try
{
// 用編譯優(yōu)化后的委托替換當前的服務(wù)訪問器,主要用到emit/expression技術(shù)
_serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
}
catch (Exception ex)
{
DependencyInjectionEventSource.Log.ServiceRealizationFailed(ex, _serviceProvider.GetHashCode());
Debug.Fail($"We should never get exceptions from the background compilation.{Environment.NewLine}{ex}");
}
},
null);
}
return result;
};
}這個實現(xiàn)的關(guān)鍵思想是,第一次解析服務(wù)時使用一個簡單的運行時解析器,這樣可以快速返回服務(wù)實例。然后,當服務(wù)再被解析,它會在后臺線程上啟動一個編譯過程,生成一個更高效的服務(wù)解析委托。一旦編譯完成,新的委托會替換掉原來的委托,以后的服務(wù)解析將使用這個新的、更高效的委托。這種方法可以在不影響應(yīng)用程序啟動時間的情況下,逐漸優(yōu)化服務(wù)解析的性能。
1.2.2 ServiceProviderEngineScope
? ServiceProviderEngineScope 閃亮登場,他是我們服務(wù)商的代言人,從定義不難看出他對外提供了服務(wù)商所具備的一切能力
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
// this scope中所有實現(xiàn)IDisposable or IAsyncDisposable的服務(wù)
private List<object>? _disposables;
// 解析過的服務(wù)緩存(其實就是scope生命周期的服務(wù)緩存,singleton生命周期的服務(wù)緩存都直接掛在調(diào)用點上了)
internal Dictionary<ServiceCacheKey, object?> ResolvedServices { get; }
// 實錘服務(wù)商代言人
public IServiceProvider ServiceProvider => this;
// 沒錯啦,通過root scope我們又能繼續(xù)創(chuàng)建無數(shù)個scope,他們彼此獨立
public IServiceScope CreateScope() => RootProvider.CreateScope();
}? 我們來觀察他獲取服務(wù)的邏輯,可以發(fā)現(xiàn)他就是很樸實無華的用著我們根服務(wù)商 ServiceProvider,去解析服務(wù),那 engine scope 呢,就是 this?,F(xiàn)在我們已經(jīng)隱約可以知道engine scope,就是為了滿足scope生命周期而生。而 ResolvedServices 中存的呢,就是該scope中的所有scope生命周期服務(wù)實例啦,在這個scope中他們是唯一的。
public object? GetService(Type serviceType)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
}? 再來看另一個核心的方法:CaptureDisposable,實現(xiàn)disposable的服務(wù)會被添加到 _disposables。
internal object? CaptureDisposable(object? service)
{
// 如果服務(wù)沒有實現(xiàn) IDisposable or IAsyncDisposable,那么不需要捕獲,直接原路返回
if (ReferenceEquals(this, service) || !(service is IDisposable || service is IAsyncDisposable))
{
return service;
}
bool disposed = false;
lock (Sync)
{
if (_disposed) // 如果scope已經(jīng)銷毀則進入銷毀流程
{
disposed = true;
}
else
{
_disposables ??= new List<object>();
_disposables.Add(service);
}
}
// Don't run customer code under the lock
if (disposed) // 這表示我們在試圖捕獲可銷毀服務(wù)時,scope就已經(jīng)被銷毀
{
if (service is IDisposable disposable)
{
disposable.Dispose();
}
else
{
// sync over async, for the rare case that an object only implements IAsyncDisposable and may end up starving the thread pool.
object? localService = service; // copy to avoid closure on other paths
Task.Run(() => ((IAsyncDisposable)localService).DisposeAsync().AsTask()).GetAwaiter().GetResult();
}
// 這種case會拋出一個ObjectDisposedException
ThrowHelper.ThrowObjectDisposedException();
}
return service;
}? 在engine scope銷毀時,其作用域中所有scope生命周期且實現(xiàn)了disposable的服務(wù)(其實就是_disposable)呢,也會被一同的銷毀。
public ValueTask DisposeAsync()
{
List<object>? toDispose = BeginDispose(); // 獲取_disposable
if (toDispose != null)
{
// 從后往前,依次銷毀服務(wù)
}
}? 那么有同學可能就要問了:單例實例既然不存在root scope中,而是單獨丟到了調(diào)用點上,那他是咋銷毀的?壓根沒看到啊,那不得泄露了?
? 其實呀,同學們并不用擔心這個問題。首先,單例服務(wù)的實例確實是緩存在調(diào)用點上,但 disable 服務(wù)仍然會被 scope 捕獲呀(在下文會詳細介紹)。在 BeginDispose 中的,我們會去判斷,如果是 singleton case,且root scope 沒有被銷毀過,我們會主動去銷毀喔~
if (IsRootScope && !RootProvider.IsDisposed()) RootProvider.Dispose();
1.3 ServiceCallSite
? ServiceCallSite 的主要職責是封裝服務(wù)解析的邏輯,它可以代表一個構(gòu)造函數(shù)調(diào)用、屬性注入、工廠方法調(diào)用等。DI系統(tǒng)使用這個抽象來表示服務(wù)的各種解析策略,并且可以通過它來生成服務(wù)實例。
internal abstract class ServiceCallSite
{
protected ServiceCallSite(ResultCache cache)
{
Cache = cache;
}
public abstract Type ServiceType { get; }
public abstract Type? ImplementationType { get; }
public abstract CallSiteKind Kind { get; }
public ResultCache Cache { get; }
public object? Value { get; set; }
public object? Key { get; set; }
public bool CaptureDisposable => ImplementationType == null ||
typeof(IDisposable).IsAssignableFrom(ImplementationType) ||
typeof(IAsyncDisposable).IsAssignableFrom(ImplementationType);
}
1.3.1 ResultCache
? 其中 ResultCache 定義了我們?nèi)绾尉彺娼馕龊蟮慕Y(jié)果
public CallSiteResultCacheLocation Location { get; set; } // 緩存位置
public ServiceCacheKey Key { get; set; } // 服務(wù)key(鍵化服務(wù)用的)? CallSiteResultCacheLocation 是一個枚舉,定義了幾個值
Root:表示服務(wù)實例應(yīng)該在根級別的IServiceProvider中緩存。這通常意味著服務(wù)實例是單例的(Singleton),在整個應(yīng)用程序的生命周期內(nèi)只會創(chuàng)建一次,并且在所有請求中共享。Scope:表示服務(wù)實例應(yīng)該在當前作用域(Scope)中緩存。對于作用域服務(wù)(Scoped),實例會在每個作用域中創(chuàng)建一次,并在該作用域內(nèi)的所有請求中共享。Dispose:盡管這個名稱可能會讓人誤解,但在ResultCache的上下文中,Dispose表示著服務(wù)是瞬態(tài)的(每次請求都創(chuàng)建新實例)。None:表示沒有緩存服務(wù)實例。
? ServiceCacheKey 結(jié)構(gòu)體就是包了一下服務(wù)標識符和一個slot,用于適配多實現(xiàn)的
internal readonly struct ServiceCacheKey : IEquatable<ServiceCacheKey>
{
public ServiceIdentifier ServiceIdentifier { get; }
public int Slot { get; } // 那最后一個實現(xiàn)的slot是0
}1.3.2 CallSiteFactory.GetCallSite
? 那我們來看看調(diào)用點是怎么創(chuàng)建的吧,其實上面已經(jīng)出現(xiàn)過一次了:
private ServiceCallSite? CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 防止棧溢出
{
return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceIdentifier, callSiteChain);
}
// 獲取服務(wù)標識符對應(yīng)的鎖,以確保在創(chuàng)建調(diào)用點時的線程安全。
// 是為了保證并行解析下的調(diào)用點也只會被創(chuàng)建一次,例如:
// C -> D -> A
// E -> D -> A
var callsiteLock = _callSiteLocks.GetOrAdd(serviceIdentifier, static _ => new object());
lock (callsiteLock)
{
// 檢查當前服務(wù)標識符是否會導致循環(huán)依賴
callSiteChain.CheckCircularDependency(serviceIdentifier);
// 首先嘗試創(chuàng)建精確匹配的服務(wù)調(diào)用站點,如果失敗,則嘗試創(chuàng)建開放泛型服務(wù)調(diào)用站點,如果還是失敗,則嘗試創(chuàng)建枚舉服務(wù)調(diào)用站點。如果所有嘗試都失敗了,callSite將為null。
ServiceCallSite? callSite = TryCreateExact(serviceIdentifier, callSiteChain) ??
TryCreateOpenGeneric(serviceIdentifier, callSiteChain) ??
TryCreateEnumerable(serviceIdentifier, callSiteChain);
return callSite;
}
}? 那服務(wù)點的創(chuàng)建過程我就簡單概述一下啦
- 查找調(diào)用點緩存,存在就直接返回啦
- 服務(wù)標識符會被轉(zhuǎn)成服務(wù)描述符
ServiceDescriptor(key化服務(wù)不指定key默認取last) - 計算
ServiceCallSite,依次是:TryCreateExact計算
ResultCache如果已經(jīng)有實現(xiàn)實例了,則返回
ConstantCallSite:表示直接返回已經(jīng)創(chuàng)建的實例的調(diào)用點。如果有實現(xiàn)工廠,則返回
FactoryCallSite:表示通過工廠方法創(chuàng)建服務(wù)實例的調(diào)用點。如果有實現(xiàn)類型,則返回
TryCreateOpenGenericConstructorCallSite:表示通過構(gòu)造函數(shù)創(chuàng)建服務(wù)實例的調(diào)用點。根據(jù)泛型定義獲取服務(wù)描述符
ServiceDescriptor計算
ResultCache使用服務(wù)標識符中的具體泛型參數(shù)來構(gòu)造實現(xiàn)的閉合類型
AOT兼容性測試(因為不能保證值類型泛型的代碼已經(jīng)生成)
如果成功閉合,則返回
TryCreateEnumerableConstructorCallSite:表示通過構(gòu)造函數(shù)創(chuàng)建服務(wù)實例的調(diào)用點。確定類型是
IEnumerable<T>AOT兼容性測試(因為不能保證值類型數(shù)組的代碼已經(jīng)生成)
如果
T不是泛型類型,并且可以找到對應(yīng)的服務(wù)描述符集合,則循環(huán) TryCreateExact否則,方向循環(huán) TryCreateExact,然后方向循環(huán) TryCreateOpenGeneric
1.4 CallSiteVisitor
? 好了,有了上面的了解我們可以開始探索服務(wù)解析的內(nèi)幕了。服務(wù)解析說白了就是引擎圍著 CallSiteVisitor 轉(zhuǎn)圈圈,所謂的解析服務(wù),其實就是訪問調(diào)用點了。
protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
{
if (!_stackGuard.TryEnterOnCurrentStack()) // 一些校驗,分棧啥的
{
return _stackGuard.RunOnEmptyStack(VisitCallSite, callSite, argument);
}
switch (callSite.Cache.Location)
{
case CallSiteResultCacheLocation.Root: // 單例
return VisitRootCache(callSite, argument);
case CallSiteResultCacheLocation.Scope: // 作用域
return VisitScopeCache(callSite, argument);
case CallSiteResultCacheLocation.Dispose: // 瞬態(tài)
return VisitDisposeCache(callSite, argument);
case CallSiteResultCacheLocation.None: // 不緩存(ConstantCallSite)
return VisitNoCache(callSite, argument);
default:
throw new ArgumentOutOfRangeException();
}
}? 為了方便展示,我們這里的解析器都拿運行時來說,因為內(nèi)部是反射,而emit、expression實在是難以觀看。
1.4.1 VisitRootCache
? 那我們來看看單例的情況下,是如何訪問的:
protected override object? VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
if (callSite.Value is object value)
{
// Value already calculated, return it directly
return value;
}
var lockType = RuntimeResolverLock.Root;
// 單例都是直接放根作用域的
ServiceProviderEngineScope serviceProviderEngine = context.Scope.RootProvider.Root;
lock (callSite)
{
// 這里搞了個雙檢鎖來確保在多線程環(huán)境中,同一時間只有一個線程可以執(zhí)行接下來的代碼塊。
// Lock the callsite and check if another thread already cached the value
if (callSite.Value is object callSiteValue)
{
return callSiteValue;
}
object? resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
// 捕獲可銷毀的服務(wù)
serviceProviderEngine.CaptureDisposable(resolved);
// 緩存解析結(jié)果到調(diào)用點上
callSite.Value = resolved;
return resolved;
}
}? 好,可以看到真正解析調(diào)用點的主角出來了 VisitCallSiteMain,那這里的 CallSiteKind 上面計算 ServiceCallSite 時呢已經(jīng)講的很清楚啦,咱對號入座就行了
protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
{
switch (callSite.Kind)
{
case CallSiteKind.Factory:
return VisitFactory((FactoryCallSite)callSite, argument);
case CallSiteKind.IEnumerable:
return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
case CallSiteKind.Constructor:
return VisitConstructor((ConstructorCallSite)callSite, argument);
case CallSiteKind.Constant:
return VisitConstant((ConstantCallSite)callSite, argument);
case CallSiteKind.ServiceProvider:
return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
default:
throw new NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported, callSite.GetType()));
}
}? 我們就看看最經(jīng)典的通過構(gòu)造函數(shù)創(chuàng)建服務(wù)實例的調(diào)用點 ConstructorCallSite,很顯然就是new嘛,只不過可能構(gòu)造中依賴其它服務(wù),那就遞歸創(chuàng)建唄。easy,其它幾種太簡單了大家自己去看看吧。
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
{
object?[] parameterValues;
if (constructorCallSite.ParameterCallSites.Length == 0)
{
parameterValues = Array.Empty<object>();
}
else
{
parameterValues = new object?[constructorCallSite.ParameterCallSites.Length];
for (int index = 0; index < parameterValues.Length; index++)
{
// 遞歸構(gòu)建依賴的服務(wù)
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
}
}
// new (xxx)
return constructorCallSite.ConstructorInfo.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: parameterValues, culture: null);
}1.4.2 VisitScopeCache
? 在訪問單例緩存的時候呢,僅僅通過了一個double check lock就搞定了,因為人家全局的嘛,咱再來看看訪問作用域緩存,會不會有什么不一樣
protected override object? VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
// Check if we are in the situation where scoped service was promoted to singleton
// and we need to lock the root
return context.Scope.IsRootScope ?
VisitRootCache(callSite, context) :
VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
}? 哈哈,它果然很不一般啊,上來就來檢查我們是否是 root scope。如果是這種case呢,則走 VisitRootCache。但是奇怪啊,為什么訪問 scope cache,所在 engine scope 能是 root scope?
? 還記得 ServiceProvider 獲取的服務(wù)實例的核心方法嗎?engine scope 他是傳進來的,如果我們給一個 root scope,自然就出現(xiàn)的這種case,只是這種 case 特別罕見。
internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
? VisitCache 的同步模型寫的實在是酷,我們看 RuntimeResolverLock 的枚舉就兩個:Scope = 1 和 Root = 2
AcquiredLocks=Scope時
那 AcquiredLocks&false==0 顯然成立,申請鎖,也就是嘗試獨占改作用域的ResolvedServices
申請成功進入同步塊,重新計算AcquiredLocks|true=1
如此,在該engine scope 中這條鏈路上的調(diào)用點都被占有,直到結(jié)束
AcquiredLocks=Root 時
- 那顯然 engine scope 也應(yīng)該是 root scope,那么走
VisitRootCachecase - 在
VisitRootCache通過DCL鎖住 root scope 上鏈路涉及的服務(wù)點,直至結(jié)束
- 那顯然 engine scope 也應(yīng)該是 root scope,那么走
? 至此我們應(yīng)該不難看出這個設(shè)計的精妙之處,即在非 root scope(scope生命周期)中,scope之間是互相隔離的,沒有必要像 root scope(singleton生命周期)那樣,在所有scope中獨占服務(wù)點。
private object? VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine
{
bool lockTaken = false;
object sync = serviceProviderEngine.Sync;
Dictionary<ServiceCacheKey, object?> resolvedServices = serviceProviderEngine.ResolvedServices;
if ((context.AcquiredLocks & lockType) == 0)
{
Monitor.Enter(sync, ref lockTaken);
}
try
{
// Note: This method has already taken lock by the caller for resolution and access synchronization.
// For scoped: takes a dictionary as both a resolution lock and a dictionary access lock.
if (resolvedServices.TryGetValue(callSite.Cache.Key, out object? resolved))
{
return resolved;
}
// scope服務(wù)的解析結(jié)果是放在engine scope的ResolvedServices上的,而非調(diào)用點
resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
serviceProviderEngine.CaptureDisposable(resolved);
resolvedServices.Add(callSite.Cache.Key, resolved);
return resolved;
}
finally
{
if (lockTaken)
{
Monitor.Exit(sync);
}
}
}1.4.3 VisitDisposeCache
? 我們看最后一個,也就是 Transient case
protected override object? VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
{
return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context));
}
? 異常的簡單,我們沿用了scope的設(shè)計,但是我們沒有進行任何緩存行為。即,每次都去訪問調(diào)用點。
到此這篇關(guān)于.NET8 依賴注入的文章就介紹到這了,更多相關(guān).NET8 依賴注入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- .NET?8新預(yù)覽版使用?Blazor?組件進行服務(wù)器端呈現(xiàn)(項目體驗)
- 國產(chǎn)化中的?.NET?Core?操作達夢數(shù)據(jù)庫DM8的兩種方式(操作詳解)
- 解決Win10無法安裝.Net Framework 3.5提示錯誤代碼0x800F081F
- ASP.NET Core異常和錯誤處理(8)
- win8/8.1系統(tǒng)安裝.net framework 3.5出現(xiàn)0x800F0906代碼錯誤的解決方法
- ASP.NET MVC5+EF6+EasyUI 后臺管理系統(tǒng)(81)-數(shù)據(jù)篩選(萬能查詢)實例
- ASP.NET 程序員都非常有用的85個工具
- 無法啟動.NET Framework NGEN v4.0.30319_X86服務(wù)的解決方法
相關(guān)文章
Web.Config文件配置之限制上傳文件大小和時間的屬性配置
在Web.Config文件中配置限制上傳文件大小與時間字符串時,是在httpRuntime httpRuntime節(jié)中完成的,需要設(shè)置以下2個屬性:maxRequestLength屬性與ExecutionTimeout屬性,感興趣的朋友可以了解下,或許對你有所幫助2013-02-02
asp.net Cookie跨域、虛擬目錄等設(shè)置方法
Cookie跨域、虛擬目錄等設(shè)置方法,需要的朋友可以參考下。2009-11-11
WPF實現(xiàn)文本描邊+外發(fā)光效果的示例代碼
這篇文章主要介紹了如何利用WPF實現(xiàn)文本描邊以及外發(fā)光的效果,文中的示例代碼講解詳細,對我們學習有一定幫助,需要的可以參考一下2022-03-03

