如何在消费类构造函数中注入泛型接口IService<T>的多个实例?
嘿,这个问题挺典型的——泛型接口的批量注入确实容易踩坑,因为每个IService<T>的封闭类型(比如IService<A>、IService<B>)都是完全独立的服务类型,没法直接用IEnumerable<IService<object>>这种方式一次性注入。下面分两种常见的DI场景给你讲清楚可行的解决办法:
方法一:通过非泛型基接口统一标识
这是最通用的方案,不管你用哪种DI容器都能搞定。核心思路是给泛型接口加一个非泛型的父接口,让所有实现类都能被归到同一个“大类”下。
首先,定义非泛型基接口:
public interface IService { // 可选:如果需要统一调用所有服务,可以加一个非泛型的DoSomething方法 void DoSomething(object obj); } // 让泛型接口继承这个非泛型接口 public interface IService<T> : IService { void DoSomething(T obj); }
然后修改你的实现类,实现非泛型接口的方法(如果加了的话):
public class ServiceA : IService<A> { public void DoSomething(A a) { // 你的业务逻辑代码 Console.WriteLine($"处理A类型对象:{a}"); } // 显式实现非泛型接口,避免对外暴露不必要的方法 void IService.DoSomething(object obj) { if (obj is A aObj) DoSomething(aObj); else throw new ArgumentException($"无法处理类型:{obj.GetType().Name}", nameof(obj)); } } // ServiceB同理 public class ServiceB : IService<B> { public void DoSomething(B b) { Console.WriteLine($"处理B类型对象:{b}"); } void IService.DoSomething(object obj) { if (obj is B bObj) DoSomething(bObj); else throw new ArgumentException($"无法处理类型:{obj.GetType().Name}", nameof(obj)); } }
接下来是DI注册环节(以.NET Core自带的Microsoft.Extensions.DependencyInjection为例):
// 在Program.cs中注册服务 var builder = WebApplication.CreateBuilder(args); // 自动扫描并注册所有实现IService<>的类,同时注册为IService builder.Services.Scan(scan => scan .FromAssemblyOf<ServiceA>() // 替换成你的程序集 .AddClasses(classes => classes.AssignableTo(typeof(IService<>))) .AsImplementedInterfaces() // 同时注册为IService<T>和IService .WithTransientLifetime()); // 如果你不想自动扫描,也可以手动注册每个服务: // builder.Services.AddTransient<IService<A>, ServiceA>(); // builder.Services.AddTransient<IService>(sp => sp.GetRequiredService<IService<A>>()); // builder.Services.AddTransient<IService<B>, ServiceB>(); // builder.Services.AddTransient<IService>(sp => sp.GetRequiredService<IService<B>>());
最后,消费类的构造函数就可以直接注入IEnumerable<IService>了:
public class Consumer { private readonly IEnumerable<IService> _allServices; // 构造函数注入所有IService实例 public Consumer(IEnumerable<IService> allServices) { _allServices = allServices; } // 示例:调用所有服务的统一方法 public void ProcessAllObjects(object[] objects) { foreach (var obj in objects) { foreach (var service in _allServices) { try { service.DoSomething(obj); } catch (ArgumentException) { // 当前服务不处理该类型,跳过 continue; } } } } // 示例:获取特定类型的服务 public void ProcessA(A aObj) { var serviceA = _allServices.OfType<IService<A>>().FirstOrDefault(); serviceA?.DoSomething(aObj); } }
方法二:利用DI容器的泛型服务检索能力(以Autofac为例)
如果你用的是Autofac,它对泛型服务的支持更灵活,不需要额外定义非泛型接口就能直接获取所有IService<T>的封闭实例。
首先是服务注册:
var builder = new ContainerBuilder(); // 自动注册所有实现IService<>封闭类型的类 builder.RegisterAssemblyTypes(typeof(ServiceA).Assembly) .AsClosedTypesOf(typeof(IService<>)) .InstancePerDependency();
然后在消费类中,你可以通过IIndex<Type, object>来按类型检索服务,或者直接注入所有符合条件的实例:
public class Consumer { // 方式1:用Autofac的IIndex按类型获取服务 private readonly IIndex<Type, object> _serviceIndex; public Consumer(IIndex<Type, object> serviceIndex) { _serviceIndex = serviceIndex; } public void ProcessA(A aObj) { var serviceType = typeof(IService<A>); if (_serviceIndex.TryGetValue(serviceType, out var service)) { ((IService<A>)service).DoSomething(aObj); } } // 方式2:注入所有对象,过滤出IService<>的实例 private readonly IEnumerable<object> _allServices; // 注意:这种方式会注入容器中所有注册的对象,需要自己过滤 public Consumer(IEnumerable<object> allServices) { _allServices = allServices.Where(s => s.GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IService<>))); } }
额外小技巧:用反射从IServiceProvider获取所有实例
如果你不想修改接口或者依赖特定DI容器,也可以在消费类中通过IServiceProvider配合反射来获取所有IService<T>的实例:
public class Consumer { private readonly List<object> _allServices = new(); public Consumer(IServiceProvider serviceProvider) { // 获取当前程序集中所有IService<>的封闭接口类型 var serviceInterfaces = typeof(IService<>).Assembly.GetTypes() .Where(t => t.IsInterface && t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IService<>)) .ToList(); // 逐个从服务提供者中获取实例 foreach (var interfaceType in serviceInterfaces) { var service = serviceProvider.GetService(interfaceType); if (service != null) _allServices.Add(service); } } }
注意哦,这种方式虽然灵活,但反射会有一定性能开销,而且类型安全上不如前两种方案,适合小项目或者临时场景用。
内容的提问来源于stack exchange,提问作者108
相关产品推荐
相关产品推荐

