C#中为何无法隐式转换派生列表至泛型IEnumerable<T>?需Cast<T>?
现有如下类型体系:
class Base {} class Derived1 : Base {} class Derived2 : Base {}
同时定义了包含派生类型集合的类:
class MyClass { List<Derived1> Derived1Collection { get; set; } List<Derived2> Derived2Collection { get; set; } }
想要实现一个泛型方法,能返回上述任意集合,例如调用GetData<Derived1>()。初始实现如下:
class MyClass { public static IEnumerable<T> GetData<T>() where T : Base => typeof(T) switch // dummy "typeof(T)" { _ when typeof(T) == typeof(Derived1) => Derived1Collection, _ when typeof(T) == typeof(Derived2) => Derived2Collection, _ => throw new NotSupportedException($"{typeof(T)} isn't supported."), }; }
但返回派生集合时出现错误:Cannot implicitly convert type List<Derived1> to IEnumerable<T>。而在每个派生集合后添加.Cast<T>()后错误消失,代码可正常运行:
_ when typeof(T) == typeof(Derived1) => Derived1Collection.Cast<T>(), _ when typeof(T) == typeof(Derived2) => Derived2Collection.Cast<T>(),
请问这是为什么?
这本质是C#泛型的编译期类型检查特性和协变限制共同导致的:
编译期类型推断的局限性
泛型方法GetData<T>的返回值是IEnumerable<T>,虽然你在switch分支里通过typeof(T)判断了实际类型,但C#编译器在编译阶段不会执行运行时的类型判断逻辑。它只会看到:当前分支返回的是List<Derived1>,而方法声明要返回IEnumerable<T>——编译器无法在编译期确认T就是Derived1,所以不会允许隐式转换。泛型接口的协变规则
虽然IEnumerable<T>是协变接口(接口定义里用了out T),允许将IEnumerable<Derived1>隐式转换为IEnumerable<Base>,但协变只适用于已知的基类/派生类关系。在泛型方法中,T是一个受where T : Base约束的类型参数,编译器无法确定T具体是Derived1、Derived2还是Base本身,所以不能直接把List<Derived1>(实现了IEnumerable<Derived1>)转换为IEnumerable<T>——因为如果T是Derived2,这个转换在运行时是完全非法的,编译器会提前阻止这种潜在的错误。.Cast<T>()的作用Cast<T>()是LINQ方法,它会在运行时尝试将集合中的每个元素转换为T类型。从编译期角度看,它的返回值是IEnumerable<T>,完全匹配方法的返回类型要求;从运行时角度看,当T是Derived1时,每个元素本身就是Derived1,转换不会有问题,只是做了一层编译期的类型“桥接”,让编译器认可这个返回值的类型。
内容的提问来源于stack exchange,提问作者Alex34758

