詳解.NET依賴注入中物件的建立與“銷燬”

尋己Tenleft發表於2023-10-16

在DI容器中註冊型別,DI容器就可以幫我們建立型別的例項;如果註冊型別實現了IAsyncDisposable或者IDisposable介面,物件銷燬時DI容器還會幫我們呼叫DisposeAsyncDispose方法。這是如何實現的呢?一起來看看吧。本文是基於Dependency Injection 8.0編寫。如果已熟練使用,可以直接從第三節開始觀看。

功能演示

說明:物件的銷燬由GC管理,這裡的“銷燬”是指呼叫Dispose方法。

先介紹一下DI容器中類的三種生命週期:Singleton(單例)、Scoped(在每個ServiceProviderEngineScope中只建立一次,可以理解為區域性單例)、Transient(每次都建立新物件)。
先定義三個代表生命週期的介面ISingletonServiceIScopedServiceITransientService;分別在實現類中列印建立資訊和Dispose資訊。並且在列印資訊裡新增了HashCode,可以觀察是哪個物件被建立和“銷燬”。程式碼在每個Scope中對每個不同生命週期的類建立2個物件,共12次呼叫,來看看實際上一共建立了幾個物件。

public interface ISingletonService { }
public interface IScopedService{ }
public interface ITransientService { }

public class SingletonService : ISingletonService, IDisposable
{
    public SingletonService()
    {
        Console.WriteLine($"{this.GetType()} 被建立了 - {this.GetHashCode()}...");
    }
    public void Dispose()
    {
        Console.WriteLine($"{this.GetType()} 被銷燬了- {this.GetHashCode()}...");
    }
}

可以看到,Singleton物件建立了1個,Scoped物件建立了2個(因為有兩個ServiceProviderEngineScope),Transient物件每次呼叫都會建立新的物件,共4個。
Dispose方法呼叫順序是,先建立的最後呼叫。

ASP.NET CORE中Scope

在ASP.NET CORE中每次請求會建立一個Scope,透過HttpContext.RequestServices可以獲取這個Scope物件,所以生命週期為Scoped的類在一次請求中只會建立一次,區域性單例。

// HttpContext.RequestServices
public override IServiceProvider RequestServices
{
    get { return ServiceProvidersFeature.RequestServices; }
    set { ServiceProvidersFeature.RequestServices = value; }
}
public class RequestServicesFeature
{
    public IServiceProvider RequestServices
    {
        get
        {
            if (!_requestServicesSet && _scopeFactory != null)
            {
                _context.Response.RegisterForDisposeAsync(this);
                //每次請求建立一個Scope
                _scope = _scopeFactory.CreateScope();
                _requestServices = _scope.ServiceProvider;
                _requestServicesSet = true;
            }
            return _requestServices!;
        }
        set
        {
            _requestServices = value;
            _requestServicesSet = true;
        }
    }
}

深入理解

要理解物件的建立和“銷燬”,ServiceProvider類是關鍵。
ServiceProvider建立和“銷燬”物件主要是透過它的兩個成員來完成的;分別是ServiceProviderEngine _engineServiceProviderEngineScope Root

  • ServiceProviderEngine這是一個抽象類,只有一個方法RealizeService,該方法返回一個建立例項的委託。該類不直接建立物件,而是提供一個建立物件的委託!
  • ServiceProviderEngineScope又可以分兩類,根scope子scope,它們的主要功能是:
    捕獲建立的物件存入List<object>? _disposables中,用於呼叫Dispose方法。

ServiceProviderEngine

透過型別資訊建立例項主要有三種辦法 1.反射,2.表示式樹,3.Emit。這些功能實現在ServiceProviderEngine的子類中,繼承關係如下:

  • ServiceProviderEngine
    • RuntimeServiceProviderEngine 反射
    • ExpressionsServiceProviderEngine 表示式樹
    • ILEmitServiceProviderEngine Emit
    • CompiledServiceProviderEngine 表示式樹和Emit的組合
      • DynamicServiceProviderEngine 預設用的這個Engine,實現方式有點難理解

下面看一下這些類的實現

internal sealed class ExpressionsServiceProviderEngine : ServiceProviderEngine
{
    //使用這個類構建表示式樹
    private readonly ExpressionResolverBuilder _expressionResolverBuilder;
    //返回一個建立物件的委託!
    public override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
    {
        return _expressionResolverBuilder.Build(callSite);
    }
}
internal sealed class ILEmitServiceProviderEngine : ServiceProviderEngine
{
    //使用這個類構建emit
    private readonly ILEmitResolverBuilder _expressionResolverBuilder;
    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
    {
        return _expressionResolverBuilder.Build(callSite);
    }
}
internal sealed class RuntimeServiceProviderEngine : ServiceProviderEngine
{   // 反射
    public static RuntimeServiceProviderEngine Instance { get; } = new RuntimeServiceProviderEngine();
    //使用反射相關方法在CallSiteRuntimeResolver中
    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
    {
        return scope => CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
    }
}
internal abstract class CompiledServiceProviderEngine : ServiceProviderEngine
{
    //透過以下方式選擇預設建立例項的方式
#if IL_EMIT
    public ILEmitResolverBuilder ResolverBuilder { get; } //emit構建
#else
    public ExpressionResolverBuilder ResolverBuilder { get; } //表示式樹構建
#endif

    public CompiledServiceProviderEngine(ServiceProvider provider)
    {
        ResolverBuilder = new(provider);
    }

    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite) => ResolverBuilder.Build(callSite);
}

預設是使用emit的方式建立物件,透過以下方法可以驗證預設使用的引擎和建立例項的方式

static void EngineInfo(ServiceProvider provider)
{
    var p = Expression.Parameter(typeof(ServiceProvider));
    //相當於 provider._engine
    var lambda = Expression.Lambda<Func<ServiceProvider, object>>(Expression.Field(p, "_engine"), p);

    var engine = lambda.Compile()(provider);
    var baseType = engine.GetType().BaseType!;
    // 相當於(provider._engine as CompiledServiceProviderEngine).ResolverBuilder
    var lambda2 = Expression.Lambda<Func<ServiceProvider, object>>(
        Expression.Property(Expression.Convert(Expression.Field(p, "_engine"), baseType), "ResolverBuilder"), p);

    var builder = lambda2.Compile()(provider);
    Console.WriteLine(engine.GetType());
    Console.WriteLine(builder.GetType());
    //輸出資訊:
    //Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine
    //Microsoft.Extensions.DependencyInjection.ServiceLookup.ILEmitResolverBuilder
}

從上面輸出可以看到DynamicServiceProviderEngine是預設的Engine,並且它的父類CompiledServiceProviderEngine是使用的ILEmitResolverBuilder
下面來介紹DynamicServiceProviderEngine,這段程式碼三年前看不懂,我三年後還是不太懂,實現方式有點難以理解,有大佬懂的可以解答下。
首先,Singleton物件是不會呼叫這個方法的,只有生命週期是ScopedTransient才會呼叫這個方法。透過呼叫RealizeService獲取一個建立物件的委託;對於每一個服務標識在呼叫2次後,替換為用emit的方式去建立例項的委託(如果不理解服務標識,看我的這篇文章),並且,還是用執行緒池實現的,對於Web開發,而CPU繫結的非同步不推薦使用執行緒池。
前幾次呼叫,反射效能應該是優於emit和表示式樹,因為後兩者有一個構建委託的過程。但是,使用執行緒池的影響應該遠大於前者吧。一開始就直接使用emit建立物件不應該優於使用執行緒池?上百個類使用執行緒池,會導致網站啟動時很卡。當然,也就啟動後一小段時間卡。

internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine
{
    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
    {
        int callCount = 0;
        return scope =>
        {
            // Resolve the result before we increment the call count, this ensures that singletons
            // won't cause any side effects during the compilation of the resolve function.
            var result = CallSiteRuntimeResolver.Instance.Resolve(callSite, scope);
            if (Interlocked.Increment(ref callCount) == 2)
            {
                //不捕獲ExecutionContext,AsyncLocal失效。
                _ = ThreadPool.UnsafeQueueUserWorkItem(_ =>
                {
                    _serviceProvider.ReplaceServiceAccessor(callSite, base.RealizeService(callSite));
                },
                null);
            }
            return result;
        };
    }
}

獲取建立物件的委託時,需要傳入引數ServiceCallSite,callSite物件有型別的建立資訊,指明如何建立這個型別的例項,比如透過建構函式、工廠、建立陣列(IEnumerable<>服務)、常量等建立例項。並且在後面呼叫建立物件的委託時,如果需要快取,會把建立出來的物件快取到callSite物件中。

ServiceProviderEngineScope

下面再來介紹下ServiceProviderEngineScope,主要成員及相關介紹在下面程式碼中。
如何建立ServiceProviderEngineScope物件呢?透過services.CreateScope()就是建立一個scope物件。建立scope物件時,會傳遞ServiceProvider並儲存到RootProvider屬性中。而每個scope物件又有一個名字為ServiceProvider的屬性,它的值是this;所以scope.ServiceProvider == scope
我們發現這個物件也有GetService方法,發現它只是轉發了一下,其實還是呼叫ServiceProviderGetService獲取物件。這個方法還把當前scope作為引數傳遞過去了,表示建立出來的物件由當前scope物件管理。
我們透過scope.ServiceProvider.GetService<ISingletonService>()獲取物件和services.GetService<ISingletonService>()基本一樣,只是傳遞的scope不同,前者是傳遞自己,後者是傳遞ServiceProvider物件中的scope物件(也就是Root屬性)。
再次說明:scope.ServiceProvider是返回自己,scope.RootProvider才是真正的ServiceProvider物件

internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
    //快取建立出來的物件,只會快取“非根Scope”建立的且生命週期為“Scoped”的例項
    internal Dictionary<ServiceCacheKey, object?> ResolvedServices { get; }
    // 存放捕獲的 disposables 物件
    private List<object>? _disposables;
    //是否為根Scope
    public bool IsRootScope { get; }
    //這裡有點繞,ServiceProvider屬性返回的是自己。每個子Scope就是 engineScope
    public IServiceProvider ServiceProvider => this;
    //RootProvider屬性是儲存的ServiceProvider物件
    internal ServiceProvider RootProvider { get; }
    //所有建立Scope的方法都是呼叫 ServiceProvider的CreateScope方法
    public IServiceScope CreateScope() => RootProvider.CreateScope();
    //唯一建構函式
    public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
    {
        ResolvedServices = new Dictionary<ServiceCacheKey, object?>();
        RootProvider = provider; //儲存ServiceProvider物件
        IsRootScope = isRootScope;//只有ServiceProvider物件中的scope物件的這個值是true,其它都是false
    }
    public object? GetService(Type serviceType)
    {
        //每個子scope還是呼叫RootProvider建立服務,只是由子scope捕獲物件用於釋放Dispose
        return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
    }
}

ServiceProvider的建構函式中,註冊了IServiceProviderIServiceScopeFactory服務。兩者都是返回一個ServiceProviderEngineScope物件,前者是返回當前scope物件,後者是返回單例根scope物件(Root屬性)。這是理解建立scope物件流程的前提。

呼叫services.CreateScope() 建立scope物件流程:

  • 1.呼叫ServiceProvider的CreateScope方法。services.CreateScope()
  • 2.CreateScope方法就是獲取根scope並呼叫它自己的CreateScope方法。provider.GetRequiredService<IServiceScopeFactory>().CreateScope()
  • 3.而根scope的CreateScope方法是呼叫ServiceProvider的CreateScope方法。RootProvider.CreateScope()
  • 4.在ServiceProvider的CreateScope方法中就是new一個物件。new ServiceProviderEngineScope(this, isRootScope: false)

兜兜轉轉,services.CreateScope()最終是呼叫自己內部方法CreateScope。程式碼和註釋已貼出,結合上下兩段程式碼看。

//這是在ServiceProvider的建構函式中註冊的服務
CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProvider)), new ServiceProviderCallSite());
CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceScopeFactory)), new ConstantCallSite(typeof(IServiceScopeFactory), Root));

//我們建立Scope是呼叫的這個擴充套件方法,結合上面程式碼,你會發現CreateScope其實是呼叫的自己的同名方法
public static IServiceScope CreateScope(this IServiceProvider provider)
{
    return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}
//這裡是ServiceProvider的CreateScope方法,它“internal”修飾的,但最終呼叫的這裡
internal IServiceScope CreateScope()
{
    // 建立Scope最終是呼叫的這裡,ServiceProvider會儲存在每個Scope裡,每個Scope都是同一個ServiceProvider
    return new ServiceProviderEngineScope(this, isRootScope: false);
}

根scope和子scope

根scope子scope透過IsRootScope屬性區分,儲存在ServiceProvider中的Root屬性就是一個根scope,其生命週期是全域性的,程式停止才會呼叫Dispose方法。ASP.NET CORE中每次請求建立的scope就是一個子scope。透過兩個例子加深印象:

static void EngineScopeTest()
{
    using var services = new ServiceCollection().BuildServiceProvider();
    using var scope1 = services.CreateScope();
    using var scope2 = scope1.ServiceProvider.CreateScope();
    //返回當前scope
    var rootScope = services.GetService<IServiceProvider>();
    //返回根scope
    var rootScope2 = services.GetService<IServiceScopeFactory>();

    var currentScope = scope1.ServiceProvider.GetService<IServiceProvider>();
    var rootScope3 = scope1.ServiceProvider.GetService<IServiceScopeFactory>();

    var currentScope2 = scope2.ServiceProvider.GetService<IServiceProvider>();
    var rootScope4 = scope2.ServiceProvider.GetService<IServiceScopeFactory>();
    // 獲取根scope
    var root = GetRoot(services);
    //執行程式碼輸出4個 True
    Console.WriteLine(rootScope == root && rootScope == (root as IServiceScope)!.ServiceProvider);
    Console.WriteLine(rootScope == rootScope3 && rootScope == rootScope4);
    Console.WriteLine(currentScope == scope1 && currentScope == scope1.ServiceProvider);
    Console.WriteLine(currentScope2 == scope2 && currentScope2 == scope2.ServiceProvider);
}
static object GetRoot(ServiceProvider provider)
{   //透過表示式樹獲取根scope
    var p = Expression.Parameter(typeof(ServiceProvider));
    //相當於 provider.Root
    var lambda = Expression.Lambda<Func<ServiceProvider, object>>(Expression.Property(p, "Root"), p);
    return lambda.Compile()(provider);
}

呼叫GetService<IServiceProvider>()返回的是當前scope,呼叫GetService<IServiceScopeFactory>()返回的是根scope。就算透過子scope方法scope1.ServiceProvider.CreateScope()建立scope,最終還是呼叫new ServiceProviderEngineScope(this, isRootScope: false)建立scope,並且子scope之間沒有層級關係,每個子scope都儲存了同一份ServiceProvider物件。
再透過以下例子驗證一下,子scope是否真的儲存了ServiceProvider物件

static void EngineScopeWhitServiceProviderTest()
{
    using var services = new ServiceCollection().BuildServiceProvider();
    using var scope1 = services.CreateScope()!;
    using var scope2 = scope1.ServiceProvider.CreateScope()!;

    var rootScope = services.GetService<IServiceProvider>();

    var currentScope = scope1.ServiceProvider.GetService<IServiceProvider>();

    var currentScope2 = scope2.ServiceProvider.GetService<IServiceProvider>();

    var provider = GetRootProvider(scope1);//獲取子scope儲存的ServiceProvider
    var provider2 = GetRootProvider(scope2);//獲取子scope儲存的ServiceProvider
    //獲取根scope的ServiceProvider
    var root = (IServiceScope)GetRoot(services);
    var provider3 = GetRootProvider(root);

    Console.WriteLine(services == provider && services == provider2 && services == provider3);
    Console.WriteLine(services != rootScope && services != currentScope && services != root.ServiceProvider);
}

static object GetRootProvider(IServiceScope scope)
{   //透過表示式樹獲取RootProvider
    var p = Expression.Parameter(typeof(IServiceScope));
    //相當於 scope.RootProvider
    var lambda = Expression.Lambda<Func<IServiceScope, object>>(
       Expression.Property(Expression.Convert(p, scope.GetType()), "RootProvider"), p);
    return lambda.Compile()(scope);
}

驗證沒問題。根scope子scope的幾個特點:

  • 都會捕獲需要釋放Dispose的物件
  • 所有Singleton物件的“銷燬”由根scope管理
  • 對於ScopedTransient的物件,誰關聯誰管理(建立物件時需要傳入一個關聯scope物件)
    • 注意:對於ScopedTransient的物件被ServiceProvider建立時,它們的生命週期就變成了Singleton,它們的Dispose方法往往需要等程式停止時才會呼叫。雖然可以在建立容器時設定引數ValidateScopes開啟校驗,但只會校驗Scoped物件,它只會禁止ServiceProvider中建立Scoped物件和Singleton物件中注入Scoped物件,對於Transient物件不會校驗,所以Transient物件的Dispose方法要等程式停止時才會呼叫。
  • 子scope會快取生命週期為“Scoped”的例項,並儲存到ResolvedServices屬性中。
    • 雖然後面會把建立物件的委託替換成了Emit的形式,但是生命週期為“Scoped”的例項已經儲存到ResolvedServices屬性中,所以後面建立的“Scoped”的例項還是同一個。
  • 對於根scope關聯的SingletonScoped物件,建立出來後都會快取,只是儲存在不同地方(建立物件的委託中或ServiceCallSite中)。

問:子scope建立的Singleton物件是由根scope管理的,那子scope是怎樣獲取根scope物件的呢?
答:前面講過,子scope會儲存ServiceProvider物件,訪問它的Root屬性就可以得到根scope。像這樣scope1.RootProvider.Root。這兩個成員的修飾符都是internal,我們要想訪問,可以參考前面的程式碼GetRootProviderGetRoot

Dispose

物件被DI容器建立出來,如果有實現Dispose介面,就會被ServiceProviderEngineScope捕獲並放入物件List<object>? _disposables中,並在DI容器本身被呼叫Dispose方法時,會逆序呼叫_disposables中每個物件的Dispose方法( for (int i = _disposables.Count - 1; i >= 0; i--)),也就是呼叫scopeDispose方法就會呼叫它捕獲物件的Dispose方法。

scopeDispose方法呼叫的時機:

  • 對於根scope,也就是ServiceProvider的Root屬性,當ServiceProviderDispose方法被呼叫時,會呼叫Root.Dispose()
  • 對於子scope需要自己手動呼叫Dispose方法或者用using

透過一段程式碼來驗證一下scope物件是否真的捕獲了物件:

static void DisposeTest()
{
    using var services = new ServiceCollection()
         .AddSingleton<ISingletonService, SingletonService2>()
         .AddScoped<IScopedService, ScopedService2>()
         .AddTransient<ITransientService, TransientService2>()
         .BuildServiceProvider();
    
    var singleton1 = services.GetService<ISingletonService>();
    var scoped1 = services.GetService<IScopedService>();
    var transient1 = services.GetService<ITransientService>();
    //獲取被捕獲的物件
    var rootDisposables = GetRootDisposables(services);

    Console.WriteLine(rootDisposables.Any(o => o == singleton1)
        && rootDisposables.Any(o => o == scoped1)
        && rootDisposables.Any(o => o == transient1)
        && rootDisposables.Count == 3);

    using (var scope1 = services.CreateScope())
    {
        var singleton2 = scope1.ServiceProvider.GetService<ISingletonService>();
        var scoped2 = scope1.ServiceProvider.GetService<IScopedService>();
        var transient2 = scope1.ServiceProvider.GetService<ITransientService>();

        var singleton3 = scope1.ServiceProvider.GetService<ISingletonService>();
        var scoped3 = scope1.ServiceProvider.GetService<IScopedService>();
        var transient3 = scope1.ServiceProvider.GetService<ITransientService>();
        //獲取被捕獲的物件
        var scopeDisposables = GetScopeDisposables(scope1.ServiceProvider);
        //Singleton儲存在Root Scope中
        Console.WriteLine(scopeDisposables.Any(o => o == singleton2) == false
            && scopeDisposables.Any(o => o == singleton3) == false
            && scopeDisposables.Count == 3);//2個Transient物件,1個Scope物件

        Console.WriteLine(scopeDisposables.Any(o => o == scoped2)
            && scopeDisposables.Any(o => o == transient2)
            && scopeDisposables.Any(o => o == scoped3)
            && scopeDisposables.Any(o => o == transient3));
    }
}
static List<object> GetRootDisposables(ServiceProvider provider)
{
    var providerParam = Expression.Parameter(typeof(ServiceProvider));
    // provider.Root
    var engineParam = Expression.Property(providerParam, "Root");
    // provider.Root._disposables;
    var dispoParam = Expression.Field(engineParam, "_disposables");
    var lambda = Expression.Lambda<Func<ServiceProvider, List<object>>>(dispoParam, providerParam);

    return lambda.Compile()(provider);
}
static List<object> GetScopeDisposables(IServiceProvider provider)
{
    //這個provider就是ServiceProviderEngineScope
    var providerParam = Expression.Parameter(typeof(IServiceProvider));
    // provider as ServiceProviderEngineScope
    var engineParam = Expression.Convert(providerParam, provider.GetType());
    // (provider as ServiceProviderEngineScope)._disposables;
    var dispoParam = Expression.Field(engineParam, "_disposables");
    var lambda = Expression.Lambda<Func<IServiceProvider, List<object>>>(dispoParam, providerParam);

    return lambda.Compile()(provider);
}

輸出3個True,沒問題。

ServiceProvider

最後再介紹一下,ServiceProvider是如何利用ServiceProviderEngineScopeServiceProviderEngine建立和管理物件的。類ServiceIdentifier是DI8.0的新功能

private sealed class ServiceAccessor
{
    //型別的建立資訊和快取
    public ServiceCallSite? CallSite { get; set; }
    //來自ServiceProviderEngine的RealizeService方法
    public Func<ServiceProviderEngineScope, object?>? RealizedService { get; set; }
}
//注意,RealizeService方法的引數和返回值就是ServiceAccessor類的成員
internal sealed class ILEmitServiceProviderEngine : ServiceProviderEngine
{
    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite)
    {
       //...
    }
}
 public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IDisposable, IAsyncDisposable
 {
     //虛擬碼,但最終是new DynamicServiceProviderEngine
     internal ServiceProviderEngine _engine = new DynamicServiceProviderEngine(this);
     //根scope 它的IsRootScope屬性為true
     internal ServiceProviderEngineScope Root { get; }

    //我們獲取服務最終都是呼叫這裡
    internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
    {
        //_serviceAccessors是一個字典,可以理解為每一個服務標識對應一個建立物件的建立物件的委託
        ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, CreateServiceAccessor);
        //呼叫 委託 獲取物件
        object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);

        return result;
    }

    private ServiceAccessor CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
    {
        //GetCallSite方法獲取型別的建立資訊,指明如何建立這個型別的例項,比如透過建構函式、工廠、建立陣列(IEnumerable<>服務)、常量等建立例項
        ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());

        if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
        {
            //Singleton物件,透過反射建立一次
            object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
            //Resolve方法裡,在物件建立出來後會儲存到callSite裡面,如果實現dispose介面還會被Root捕獲
            return new ServiceAccessor { CallSite = callSite, RealizedService = scope => value };
        }
        //呼叫 ServiceProviderEngine 獲取一個 建立物件的委託
        Func<ServiceProviderEngineScope, object?> realizedService = _engine.RealizeService(callSite);
        return new ServiceAccessor { CallSite = callSite, RealizedService = realizedService };
    }
}

程式碼只保留了核心邏輯。建立物件時,所有的GetService方法最終都是呼叫這個方法GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)。該方法接收一個“服務標識”和一個“ServiceProviderEngineScope”物件,如果是透過services.GetService呼叫,傳遞的就是根scope;如果是透過scope1.ServiceProvider.GetService呼叫,傳遞的就是當前子scope物件。
GetService方法先透過CreateServiceAccessor獲取“服務標識”相關資訊(ServiceCallSite)和建立物件的委託。ServiceCallSite前面有介紹。Singleton物件不會透過ServiceProviderEngineRealizeService方法獲取建立物件的委託,而是直接透過反射建立物件並快取起來。ScopedTransient物件透過 _engine.RealizeService(callSite)獲取建立物件的委託。最後就是呼叫委託獲取物件。

總結

.NET依賴注入中獲取物件就是透過GetService方法,方法內容主要就兩步:

  • 1.獲取建立物件的委託(透過ServiceProviderEngine
  • 2.呼叫委託建立物件

如果建立出來的物件實現了IAsyncDisposable或者IDisposable介面,建立出來的物件會被ServiceProviderEngineScope捕獲用於呼叫Dispose方法。
如有疑問,歡迎評論交流。如有錯誤,歡迎批評指出。
寫文章是真累!以後儘量多寫文章,這樣也能放在簡歷上了。

相關文章