You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在运行时切换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实现容器级的动态注册更新:

  1. 安装NuGet包:Autofac、Autofac.Extensions.DependencyInjection
  2. 改造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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 15:19:55