在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()}...");
    }
}

详解.NET依赖注入中对象的创建与“销毁”-LMLPHP

可以看到,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获取一个创建对象的委托;对于每一个服务标识

10-16 09:10