带where T:BaseType约束的GenericClass<T>为何无法转为GenericClass<BaseType>?
GenericClass<T>不能转换为GenericClass<BaseType>? 这是个很常见的泛型类型认知误区——虽然你的T被约束为BaseType的子类,但C#里普通泛型类默认是「不变」的,也就是说GenericClass<Derived>和GenericClass<BaseType>之间不存在任何继承或转换关系,哪怕Derived : BaseType。
为什么默认是不变的?
举个简单的例子就能理解:假设你的GenericClass<T>里有一个这样的方法:
void Add(T item) { /* 往内部集合加元素 */ }
如果允许把GenericClass<Derived>强制转换成GenericClass<BaseType>,那你就可以调用Add(new BaseType()),这显然会破坏类型安全——原来的集合只应该存放Derived类型的对象,现在却塞进了父类对象,后续操作肯定会出问题。C#的类型系统就是为了避免这种不安全的情况,才默认让泛型类保持不变。
两种解决思路
1. 只读场景:使用协变接口
如果你的GenericClass<T>只对外暴露输出T的操作(比如返回T的属性、返回T的方法),完全没有接收T作为输入的操作,那可以定义一个带out关键字的协变接口:
// 定义协变接口,out标记表示T只能被输出 interface IGenericClass<out T> where T : BaseType { } class GenericClass<T> : IGenericClass<T> where T : BaseType { void Method() { new UtilityClass().Method(this); // 现在可以正常转换了 } } class UtilityClass { // 参数改成协变接口类型 internal void Method(IGenericClass<BaseType> genericClass) { } }
out T告诉编译器:这个接口只会向外提供T类型的数据,不会接收T类型的输入,所以把IGenericClass<Derived>转成IGenericClass<BaseType>是安全的。
2. 有写操作场景:把Utility的方法改成泛型
如果你的GenericClass<T>需要支持修改T的操作(比如接受T作为参数的方法),协变就用不了了。这时候可以把UtilityClass的Method也改成泛型方法,带上同样的约束:
class UtilityClass { internal void Method<T>(GenericClass<T> genericClass) where T : BaseType { } }
这样方法就能接受任意符合约束的GenericClass<T>实例,不需要做任何类型转换。
内容的提问来源于stack exchange,提问作者Manuel

