为何List<List<int>>无法隐式转换为IList<IList<int>>?
先看你提供的测试代码:
internal class Program { static void Main(string[] args) { int[][] jaggedArray = { new int[] { 0 } }; Console.WriteLine(AcceptsIEnumerable(jaggedArray)); Console.WriteLine(AcceptsIList(jaggedArray)); List<List<int>> listArray = new() { new List<int> { 0 } }; Console.WriteLine(AcceptsIEnumerable(listArray)); // Not valid! Why? //Console.WriteLine(AcceptsIList(listArray)); } static bool AcceptsIEnumerable(IEnumerable<IEnumerable<int>> x) { return true; } static bool AcceptsIList(IList<IList<int>> x) { return true; } }
核心原因是接口的可变性规则和类型安全约束,拆成三点讲清楚:
1. 为什么IEnumerable<IEnumerable>能接收List<List>?
IEnumerable<T>是协变接口(声明时用了out T关键字),协变的核心逻辑是:如果A是B的实现类/子类,那么IEnumerable<A>可以隐式转成IEnumerable<B>。
这里List<int>实现了IEnumerable<int>,所以List<int>属于IEnumerable<int>的“兼容类型”。外层的IEnumerable<IEnumerable<int>>只要求集合能产出IEnumerable<int>类型的元素,List<List<int>>刚好满足——它的每个元素都是List<int>,也就是合法的IEnumerable<int>,所以转换能通过。
2. 为什么IList<IList>不能接收List<List>?
IList<T>是不变接口(声明时没有in或out修饰T),因为它同时包含读操作(比如this[int index]获取元素)和写操作(比如Add(T item)添加元素)。
如果允许List<List<int>>转成IList<IList<int>>,会直接破坏类型安全:
- 在
AcceptsIList函数里,你可以调用x.Add(new int[0])——因为int[]也实现了IList<int>,符合接口的参数要求。 - 但原来的
List<List<int>>只能添加List<int>类型的元素,强行加入int[]会导致运行时类型转换异常。
C#为了避免这种潜在的崩溃,直接在编译阶段就禁止了这种转换。
3. 为什么int[][]能传入IList<IList>?
交错数组int[][]本质是int[]的数组,而int[]本身实现了IList<int>。同时C#对数组有特殊的协变支持(仅限引用类型数组),int[][]可以隐式转换成IList<IList<int>>——虽然调用Add方法会抛出NotSupportedException(因为数组长度固定),但编译时是允许的,因为它结构上符合接口的要求。
简单总结:
- 协变接口(如
IEnumerable<T>)允许“子类型集合”转成“父类型集合”,前提是只做读操作。 - 不变接口(如
IList<T>)因为支持写操作,必须严格匹配类型,杜绝类型安全风险。 - 数组有特殊的协变规则,所以
int[][]能绕过编译限制(但运行时写操作会报错)。
内容的提问来源于stack exchange,提问作者andypea

