如何让C#编译器在传入List<U>时自动推断IList<IInterface<T>>类型?
我定义了如下泛型方法:
public void Proc<T>(string paramName, IList<IInterface<T>> paramValue) where T : AncestorType, new() { }
当传入List<U>(其中U是实现IInterface<T>的类,且T继承自AncestorType)调用该方法时,重载解析无法正常工作。
我尝试过两种替代方案,但都不够优雅:
- 改用双泛型参数的方法,但调用时必须显式指定类型参数:
public void Proc<R, T>(string paramName, IList<R> paramValue) where R : IInterface<T> where T : AncestorType, new() { }
- 在LINQ查询的
ToList()中显式生成IInterface<T>类型的集合,但操作繁琐。
请问是否存在一种优雅的解决方案,既能让编译器自动完成类型推断,又无需使用显式转换?
问题的核心是IList<T>是不变类型——它既不支持协变也不支持逆变,所以List<U>没法直接隐式转成IList<IInterface<T>>,哪怕U实现了IInterface<T>。要解决这个问题,你可以利用.NET里支持协变的只读集合接口,同时让编译器自动完成类型推断:
方案1:改用协变接口当参数类型
如果你的Proc方法只需要读取集合内容(不用添加、修改元素),直接把参数类型从IList<IInterface<T>>换成IEnumerable<IInterface<T>>或者IReadOnlyList<IInterface<T>>就行,这俩接口都是协变的(泛型参数用out关键字定义):
public void Proc<T>(string paramName, IEnumerable<IInterface<T>> paramValue) where T : AncestorType, new() { }
这时候List<U>能直接隐式转成IEnumerable<IInterface<T>>,编译器会自动推断出T的类型,不用你显式指定或者做转换。
要是方法需要随机访问集合元素(比如用索引器),就换成IReadOnlyList<IInterface<T>>:
public void Proc<T>(string paramName, IReadOnlyList<IInterface<T>> paramValue) where T : AncestorType, new() { }
方案2:加个辅助重载(兼容要修改集合的场景)
如果必须保留IList的可修改能力,可以加个辅助重载,让编译器通过协变推断类型,再内部转到原方法:
// 原方法不动 public void Proc<T>(string paramName, IList<IInterface<T>> paramValue) where T : AncestorType, new() { // 原来的逻辑 } // 新增辅助重载,靠协变让编译器自动推断T public void Proc<T, U>(string paramName, IList<U> paramValue) where U : IInterface<T> where T : AncestorType, new() { // 把List<U>转成IList<IInterface<T>>,这里用Cast再ToList就行,因为U实现了IInterface<T> Proc(paramName, paramValue.Cast<IInterface<T>>().ToList()); }
不过这种方式会创建新的集合副本,对性能敏感的话得注意,但它能满足自动类型推断、不用调用者显式转换的需求。
关键提醒
- 协变接口只适用于只读场景(泛型参数只作为返回值或者只读属性),这也是
IEnumerable<out T>和IReadOnlyList<out T>支持协变的原因。 - 如果你的业务逻辑必须修改传入的集合(比如加元素),那没法避免显式转换或者创建新集合——毕竟
IList<T>的不变性是为了类型安全:要是允许List<U>转成IList<IInterface<T>>,就可能往集合里加其他实现IInterface<T>的类型,破坏原集合的类型一致性。
内容的提问来源于stack exchange,提问作者Anthony Mills

