Autofac如何避免释放通过ServiceCollection注册的服务?
通过ServiceCollection注册服务可以实现在不同DI容器(如Autofac、Simple Injector、Microsoft.Extensions.DependencyInjection)间切换,但在Autofac中复用ServiceCollection时,外部创建的IDisposable服务会被意外释放——而直接用Autofac注册时,我们可以通过.ExternallyOwned()避免这个问题。
以下是几种可行的解决方案:
方法1:修改ServiceCollection注册的释放权限
在注册外部实例时,通过显式创建ServiceDescriptor并设置AllowDisposal = false,禁止DI容器跟踪并释放该实例。
using Autofac; using Autofac.Extensions.DependencyInjection; using DI_Sandbox.Models; using Microsoft.Extensions.DependencyInjection; var externalService = new ConsoleMessageWriter(); var services = new ServiceCollection(); // 显式配置服务描述符,禁止容器释放实例 services.Add(new ServiceDescriptor( typeof(IMessageWriter), externalService, ServiceLifetime.Singleton) { AllowDisposal = false }); var builder = new ContainerBuilder(); builder.Populate(services); var container = builder.Build(); using(var serviceProvider = new AutofacServiceProvider(container)) { var messageWriter = serviceProvider.GetRequiredService<IMessageWriter>(); messageWriter.Write("Hello DI!"); } Console.Write("THE END");
预期输出:
Hello DI! THE END
方法2:在Autofac中覆盖ServiceCollection的注册
跳过Populate中对应的服务注册,直接在Autofac里重新注册目标实例并标记为ExternallyOwned(),强制Autofac不管理该实例的生命周期。
using Autofac; using Autofac.Extensions.DependencyInjection; using DI_Sandbox.Models; using Microsoft.Extensions.DependencyInjection; var externalService = new ConsoleMessageWriter(); var services = new ServiceCollection(); services.AddSingleton<IMessageWriter>(externalService); var builder = new ContainerBuilder(); builder.Populate(services); // 覆盖原有注册,标记实例为外部拥有 builder.RegisterInstance<IMessageWriter>(externalService).ExternallyOwned(); var container = builder.Build(); using(var serviceProvider = new AutofacServiceProvider(container)) { var messageWriter = serviceProvider.GetRequiredService<IMessageWriter>(); messageWriter.Write("Hello DI!"); } Console.Write("THE END");
预期输出与方法1一致。
方法3:批量修改Autofac注册的所有权
如果需要批量处理多个外部实例,可以在Populate后遍历Autofac的注册项,将目标实例的所有权设置为外部拥有。
using Autofac; using Autofac.Core; using Autofac.Extensions.DependencyInjection; using DI_Sandbox.Models; using Microsoft.Extensions.DependencyInjection; var externalService = new ConsoleMessageWriter(); var services = new ServiceCollection(); services.AddSingleton<IMessageWriter>(externalService); var builder = new ContainerBuilder(); builder.Populate(services); // 遍历注册项,找到目标实例并修改所有权 foreach (var registration in builder.ComponentRegistry.Registrations) { if (registration.Services.Contains(new TypedService(typeof(IMessageWriter))) && registration is ComponentRegistration compReg && compReg.Activator is InstanceActivator activator && activator.Instance == externalService) { compReg.Ownership = InstanceOwnership.ExternallyOwned; } } var container = builder.Build(); using(var serviceProvider = new AutofacServiceProvider(container)) { var messageWriter = serviceProvider.GetRequiredService<IMessageWriter>(); messageWriter.Write("Hello DI!"); } Console.Write("THE END");
原理说明
默认情况下,使用ServiceCollection.AddSingleton(instance)注册时,AllowDisposal属性为true,Microsoft DI会跟踪该实例的生命周期;Autofac在执行Populate时会继承这一行为,因此容器释放时会调用实例的Dispose方法。
通过设置AllowDisposal = false或在Autofac中标记ExternallyOwned(),可以明确告知容器:该实例由外部代码负责管理,容器无需处理其释放逻辑。
内容的提问来源于stack exchange,提问作者Andrey Bushman

