IoC容器协变服务解析失败求助:纯DI可行但Autofac/DryIoc无法解析
这是个非常典型的纯DI转IoC容器时会踩的坑——咱们先理清楚背后的原因,再看具体解决方案:
为什么纯DI能行,IoC容器默认不行?
在纯DI里,IQuery<string, object> service = new FakeRepository();能正常工作,是因为C#编译器会自动检查并允许符合方差规则的类型转换:你的IQuery<in TIn, out TOut>接口中,TIn是逆变(in)、TOut是协变(out),所以IQuery<object, string>可以安全转换为IQuery<string, object>(string是object的子类,逆变参数允许用更具体的类型;string可向上转换为object,协变参数允许用更宽泛的类型)。
但绝大多数IoC容器默认是精确类型匹配的:你注册的是IQuery<object, string>,容器就只会把这个类型和FakeRepository绑定,不会主动识别所有符合方差规则的兼容接口类型,所以直接解析IQuery<string, object>会找不到对应的注册。
现有容器的解决方案
1. DryIoc:内置支持,只需开启配置
DryIoc本身支持协变/逆变,但默认未启用该特性。创建容器时开启WithVariants()规则即可:
var container = new Container(rules => rules.WithVariants()); container.Register<IQuery<object, string>, FakeRepository>(); // 现在可以正常解析 var service = container.Resolve<IQuery<string, object>>();
开启后,容器会自动识别并处理符合C#方差规则的接口转换。
2. Autofac:手动注册或自定义注册源
Autofac默认不自动处理方差,你有两种解决方式:
- 手动注册兼容接口:直接把方差兼容的接口类型也绑定到同一个实现类:
var builder = new ContainerBuilder(); builder.RegisterType<FakeRepository>() .As<IQuery<object, string>>() .As<IQuery<string, object>>(); // 手动添加兼容接口 var container = builder.Build(); var service = container.Resolve<IQuery<string, object>>(); - 自定义注册源:如果有大量这类需要方差处理的接口,可以实现
IRegistrationSource来自动为请求的接口查找兼容的注册,避免逐个手动注册。
3. Simple Injector:原生支持方差
Simple Injector对协变/逆变的支持非常完善,默认就会自动处理这类类型转换,无需额外配置:
var container = new Container(); container.Register<IQuery<object, string>, FakeRepository>(); // 直接解析即可 var service = container.GetInstance<IQuery<string, object>>();
你的需求是否合理?
完全合理!协变逆变是C#类型系统的核心特性之一,你的场景是合法利用这些特性复用服务——纯DI能正常工作的逻辑,IoC容器没有理由不支持。这不是你的需求有问题,而是部分容器需要额外配置来适配语言特性。
内容的提问来源于stack exchange,提问作者Andrey

