为何C#无法隐式将Container<Derived>转换为Container<Base>?
代码示例
class Animal {} class Dog : Animal {} interface IMyContainer<T> { T Get(int i); void Add (T thing); } class MyList<T> : IMyContainer<T> { private List<T> items = new List<T>(); public T Get(int i) { return items[i]; } public void Add(T item) { items.Add(item); } } public static void Main(string[] args) { IMyContainer<Dog> dogs = new MyList<Dog>(); // 为何无法将dogs转换为animals类型? IMyContainer<Animal> animals = dogs; animals.Add(new Animal()); Animal retrievedAnimal = animals.Get(0); animals.Add(new Dog()); }
疑问背景与核心问题
已知上述代码会因泛型不变性报错,也理解IMyContainer<Animal> animals = dogs;执行失败的根本原因:MyList<Dog>本质仍是存储Dog的容器,若允许此类转换,后续通过animals引用添加非Dog的Animal子类(如Cat)会破坏容器的类型安全性。
核心疑问:为什么编译器不在赋值时直接将dogs对象转换为真正的IMyContainer<Animal>类型?这样后续就能自由添加任意Animal实例,所有类型问题不就解决了?这种隐式转换对编译器来说是无法实现吗?
解答
编译器无法完成这类转换的核心原因如下:
1. 对象实例的类型是固定不可变的
你创建的new MyList<Dog>()是一个类型固定的具体实例,其内部的List<T>本质是List<Dog>,只能容纳Dog及其子类。编译器无法凭空修改这个实例的类型——这相当于要创建一个全新的MyList<Animal>实例,而非简单的引用别名转换。
2. 引用转换的语义不允许这种操作
C#中引用类型的隐式转换(如Dog转Animal)是引用别名,转换前后指向的是同一个对象。如果编译器按你的想法执行转换,要么违背引用转换的语义(实际创建新对象),要么回到最初的类型安全问题(原MyList<Dog>实例被错误地允许添加非Dog对象)。
如果确实需要IMyContainer<Animal>,你需要手动创建新容器并复制原有元素:
IMyContainer<Animal> animals = new MyList<Animal>(); // 假设实现遍历逻辑,逐个复制元素 for (int i = 0; /* 添加遍历终止条件 */ ) { animals.Add(dogs.Get(i)); }
3. 泛型协变/逆变的限制
只有当泛型接口被标记为协变(out T,仅输出T)或逆变(in T,仅输入T)时,才能进行有限的跨类型引用转换。但你的IMyContainer<T>同时包含输出T的Get方法和输入T的Add方法,既无法标记为协变也无法标记为逆变,因此编译器不允许任何跨类型的引用转换。
内容的提问来源于stack exchange,提问作者MukundKS

