如何在运行时切换DI中Service的不同库实现
可以实现运行时切换Service的实现
针对你的场景,分两种常见需求给出具体实现方案:
一、启动时根据配置动态选择库(基于现有代码改造)
这种方式适合在应用启动前,根据用户配置或本地设置决定加载LibraryA还是LibraryB,实现简单且符合DI原生流程。
1. 改造RegisterServices方法,支持传入库名称参数
优化原代码的类型筛选逻辑,精准匹配Service接口的实现类:
public static void RegisterServices(IServiceCollection services, string libraryName) { string dllFileName = $"{libraryName}.dll"; var mobileAssembly = Assembly.GetExecutingAssembly(); var resourceName = $"Application.Libraries.{dllFileName}"; var destinationPath = Path.Combine(FileSystem.AppDataDirectory, dllFileName); // 优化:仅当文件不存在时才提取嵌入式资源,避免重复IO操作 if (!File.Exists(destinationPath)) { using (Stream? resourceStream = mobileAssembly.GetManifestResourceStream(resourceName)) { if (resourceStream == null) throw new InvalidOperationException($"未找到嵌入式资源:{resourceName}"); using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write)) { resourceStream.CopyTo(fileStream); } } } var assembly = Assembly.LoadFrom(destinationPath); // 精准筛选实现了Service接口的非抽象类 var serviceImplTypes = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && typeof(Service).IsAssignableFrom(t)); foreach (var implType in serviceImplTypes) { var interfaceType = typeof(Service); // 移除已存在的同接口注册 var existingDescriptor = services.FirstOrDefault(d => d.ServiceType == interfaceType); if (existingDescriptor != null) { services.Remove(existingDescriptor); } // 注册新的实现类 services.AddSingleton(interfaceType, implType); } }
2. 在应用启动时读取配置选择库
比如从Preferences读取用户之前保存的选择:
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiCommunityToolkit() .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); builder.Services.AddSingleton<App>(); // 读取运行时配置,默认用LibraryA string selectedLibrary = Preferences.Get("SelectedServiceLibrary", "LibraryA"); RegisterServices(builder.Services, selectedLibrary); var app = builder.Build(); return app; }
二、运行中动态切换实现(突破DI容器不可变限制)
Maui默认的ServiceProvider是不可变的,一旦构建完成就无法修改注册。要实现运行中切换,可采用以下两种方案:
方案1:使用工厂模式包装Service接口
定义一个单例工厂类,负责管理当前Service实例的切换:
public class ServiceFactory { private Service _currentService; public Service CurrentService => _currentService ?? throw new InvalidOperationException("未初始化Service实例"); public void SwitchToLibrary(string libraryName) { string dllFileName = $"{libraryName}.dll"; var destinationPath = Path.Combine(FileSystem.AppDataDirectory, dllFileName); // 复用资源提取逻辑 if (!File.Exists(destinationPath)) { var mobileAssembly = Assembly.GetExecutingAssembly(); var resourceName = $"Application.Libraries.{dllFileName}"; using (Stream? resourceStream = mobileAssembly.GetManifestResourceStream(resourceName)) { if (resourceStream == null) throw new InvalidOperationException($"未找到嵌入式资源:{resourceName}"); using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write)) { resourceStream.CopyTo(fileStream); } } } var assembly = Assembly.LoadFrom(destinationPath); var implType = assembly.GetTypes() .FirstOrDefault(t => t.IsClass && !t.IsAbstract && typeof(Service).IsAssignableFrom(t)); if (implType == null) throw new InvalidOperationException($"{libraryName}中未找到Service接口的实现类"); // 创建新实例并替换当前实例 _currentService = (Service)Activator.CreateInstance(implType); } }
注册与使用:
// 在CreateMauiApp中注册工厂为单例 var serviceFactory = new ServiceFactory(); serviceFactory.SwitchToLibrary("LibraryA"); // 初始化默认实现 builder.Services.AddSingleton(serviceFactory); // 在业务代码中注入工厂,获取当前实现 public class SomeViewModel { private readonly ServiceFactory _serviceFactory; public SomeViewModel(ServiceFactory serviceFactory) { _serviceFactory = serviceFactory; } public void SwitchToLibraryB() { _serviceFactory.SwitchToLibrary("LibraryB"); // 之后通过_serviceFactory.CurrentService使用新实现 } }
方案2:使用支持动态更新的DI容器(如Autofac)
如果项目允许替换默认DI容器,可使用Autofac实现容器级的动态注册更新:
- 安装NuGet包:
Autofac、Autofac.Extensions.DependencyInjection - 改造MauiProgram的容器构建逻辑:
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); // ... 原Maui配置代码 ... // 创建Autofac容器构建器 var autofacBuilder = new ContainerBuilder(); // 导入Maui默认注册的服务 autofacBuilder.Populate(builder.Services); // 初始注册Service实现 RegisterAutofacServices(autofacBuilder, "LibraryA"); // 构建Autofac容器并替换Maui默认服务提供者 return builder.UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureContainer<ContainerBuilder>(b => b.RegisterInstance(autofacBuilder.Build())) .Build(); } private static void RegisterAutofacServices(ContainerBuilder builder, string libraryName) { // 复用dll提取和类型查找逻辑(同方案1) string dllFileName = $"{libraryName}.dll"; var destinationPath = Path.Combine(FileSystem.AppDataDirectory, dllFileName); if (!File.Exists(destinationPath)) { var mobileAssembly = Assembly.GetExecutingAssembly(); var resourceName = $"Application.Libraries.{dllFileName}"; using (Stream? resourceStream = mobileAssembly.GetManifestResourceStream(resourceName)) { if (resourceStream == null) throw new InvalidOperationException($"未找到嵌入式资源:{resourceName}"); using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write)) { resourceStream.CopyTo(fileStream); } } } var assembly = Assembly.LoadFrom(destinationPath); var implType = assembly.GetTypes() .FirstOrDefault(t => t.IsClass && !t.IsAbstract && typeof(Service).IsAssignableFrom(t)); if (implType != null) { builder.RegisterType(implType).As<Service>().SingleInstance(); } }
运行时切换:
通过创建新的容器作用域,覆盖Service的注册:
// 获取Autofac容器实例 var container = (IContainer)App.Current.Services.GetService<IServiceProvider>(); // 创建新作用域并注册新实现 using (var scope = container.BeginLifetimeScope(builder => { builder.RegisterType<ImplB>().As<Service>().SingleInstance(); })) { // 在该作用域内使用新的Service实现 var service = scope.Resolve<Service>(); // 执行业务逻辑 }
注意事项
- 确保LibraryA、LibraryB的dll文件在项目中设置为嵌入式资源(右键文件→属性→生成操作选择"嵌入式资源")
- iOS平台对动态加载程序集有额外限制,需在项目配置中开启相关权限
- 运行时切换实例时,若旧Service实例持有资源,需手动清理避免内存泄漏
内容的提问来源于stack exchange,提问作者infinitesimal
相关产品推荐
相关产品推荐

