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

.NET Core ServiceCollection迁移:ServiceLocator带键获取实例求助

解决WPF从MVVMLight SimpleIoc迁移至MSDI后带键获取实例的问题

MSDI(Microsoft.Extensions.DependencyInjection)原生不支持像SimpleIoc那样通过唯一键直接获取实例,需要通过扩展实现命名/键控服务的注册与解析。以下分两种场景给出解决方案:

场景1:基于.NET 8+原生键依赖注入(推荐)

.NET 8正式引入了键控服务(Keyed Services),可以直接实现类似SimpleIoc的带键实例管理:

1. 注册带键服务

var services = new ServiceCollection();

// 注册同一服务类型的不同实例,指定唯一键
services.AddKeyedTransient<IMyService, MyServiceImpl>("instance-1");
services.AddKeyedTransient<IMyService, AnotherServiceImpl>("instance-2");
// 动态生成唯一键注册实例
var dynamicKey = Guid.NewGuid().ToString();
services.AddKeyedTransient<IMyService, MyServiceImpl>(dynamicKey);

2. 自定义ServiceLocator适配MSDI键控服务

MVVMLight的ServiceLocator默认不支持键参数,需要自定义实现适配:

public class MsdiKeyedServiceLocator : IServiceLocator
{
    private readonly IServiceProvider _provider;

    public MsdiKeyedServiceLocator(IServiceProvider provider)
    {
        _provider = provider;
    }

    public object GetInstance(Type serviceType) => _provider.GetRequiredService(serviceType);
    public object GetInstance(Type serviceType, string key) => _provider.GetRequiredKeyedService(serviceType, key);
    public T GetInstance<T>() => _provider.GetRequiredService<T>();
    public T GetInstance<T>(string key) => _provider.GetRequiredKeyedService<T>(key);
    
    public IEnumerable<object> GetAllInstances(Type serviceType) => _provider.GetServices(serviceType);
    public IEnumerable<T> GetAllInstances<T>() => _provider.GetServices<T>();
    public void Release(object instance) { /* MSDI根容器不支持手动释放,Scope场景可自行扩展 */ }
}

3. 配置ServiceLocator

var serviceProvider = services.BuildServiceProvider();
ServiceLocator.SetLocatorProvider(() => new MsdiKeyedServiceLocator(serviceProvider));

// 与原代码调用方式一致
var instance = ServiceLocator.Current.GetInstance<IMyService>(Guid.NewGuid().ToString());

场景2:.NET 8以下版本自定义命名服务扩展

如果项目基于.NET 8之前的版本,需要手动实现命名服务的注册与解析逻辑:

1. 扩展ServiceCollection支持命名注册

public static class NamedServiceExtensions
{
    private static readonly Dictionary<Type, Dictionary<string, Type>> _namedServices = new();

    public static IServiceCollection AddNamedTransient<TService, TImpl>(this IServiceCollection services, string key)
        where TService : class where TImpl : class, TService
    {
        services.AddTransient<TImpl>();
        
        // 维护命名服务映射表
        if (!_namedServices.ContainsKey(typeof(TService)))
            _namedServices[typeof(TService)] = new Dictionary<string, Type>();
        _namedServices[typeof(TService)][key] = typeof(TImpl);
        
        return services;
    }

    // 扩展IServiceProvider支持按键获取实例
    public static TService GetNamedService<TService>(this IServiceProvider provider, string key)
    {
        if (_namedServices.TryGetValue(typeof(TService), out var serviceMap) && serviceMap.TryGetValue(key, out var implType))
        {
            return (TService)provider.GetRequiredService(implType);
        }
        throw new KeyNotFoundException($"No named service found for key: {key}");
    }
}

2. 自定义ServiceLocator适配命名服务

public class MsdiNamedServiceLocator : IServiceLocator
{
    private readonly IServiceProvider _provider;

    public MsdiNamedServiceLocator(IServiceProvider provider)
    {
        _provider = provider;
    }

    public object GetInstance(Type serviceType) => _provider.GetRequiredService(serviceType);
    public object GetInstance(Type serviceType, string key)
    {
        // 通过反射调用GetNamedService扩展方法
        var method = typeof(NamedServiceExtensions).GetMethod("GetNamedService")?.MakeGenericMethod(serviceType);
        return method?.Invoke(null, new object[] { _provider, key }) ?? throw new KeyNotFoundException($"Key {key} not found for service {serviceType.Name}");
    }
    public T GetInstance<T>() => _provider.GetRequiredService<T>();
    public T GetInstance<T>(string key) => _provider.GetNamedService<T>(key);
    
    // 实现其他IServiceLocator方法(同.NET 8场景)
    public IEnumerable<object> GetAllInstances(Type serviceType) => _provider.GetServices(serviceType);
    public IEnumerable<T> GetAllInstances<T>() => _provider.GetServices<T>();
    public void Release(object instance) { }
}

3. 配置与使用

var services = new ServiceCollection();
services.AddNamedTransient<IMyService, MyServiceImpl>(Guid.NewGuid().ToString());

var serviceProvider = services.BuildServiceProvider();
ServiceLocator.SetLocatorProvider(() => new MsdiNamedServiceLocator(serviceProvider));

// 调用方式与原代码一致
var instance = ServiceLocator.Current.GetInstance<IMyService>("your-key");

注意事项

  • 若只是需要每次获取新实例,MSDI的Transient生命周期默认每次调用GetService都会返回新实例,无需额外键控;
  • ServiceLocator本身属于反模式,建议逐步迁移为构造函数注入,减少对ServiceLocator的依赖;
  • 若使用Scope(比如WPF的窗口级生命周期),需要在Scope内创建ServiceLocator实例,避免根容器的生命周期问题。

内容的提问来源于stack exchange,提问作者KathirVey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 22:02:53