.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
相关产品推荐
相关产品推荐

