C#继承类调用父类方法时类型推断失败的原因及泛型实现咨询
C#继承类调用父类方法时类型推断失败的原因及泛型实现咨询
最近在C#里处理继承类的方法链式调用时,碰到了一个特别绕的类型推断问题,折腾了好一会儿才搞明白,先给大伙看看我最开始写的简化代码:
namespace Demo { public class ClassA { public ClassA AddBlock(ClassA classA) => this; } public class ClassB : ClassA { //public ClassB AddBlock(ClassA classA) => this; } public class ClassC { public ClassC AddBlock(ClassB classB) => this; } class Program { static void Main() { var classB = new ClassB(); var classC = new ClassC(); var newBlockB01 = classB; newBlockB01.AddBlock(classB); var newBlockB02 = (ClassB)classB.AddBlock(classB); var newBlockB03 = classB.AddBlock(classB); classC.AddBlock(newBlockB01); // Works as expected classC.AddBlock(newBlockB02); // Works as expected classC.AddBlock(newBlockB03); //Does not work Error CS1503 } } }
当时我就纳闷了——ClassB明明继承自ClassA,AddBlock是父类里定义的方法,我用ClassB实例调用它,为啥编译器不能自动推断返回值是ClassB呢?为啥非得强制转成ClassB才能传给ClassC.AddBlock(这个方法只接受ClassB类型参数)?
其实核心原因很简单:C#是静态类型语言,方法的返回类型是在编译期就确定好的。ClassA里的AddBlock明确返回的是ClassA类型,哪怕你用ClassB实例去调用它,编译器只会认方法定义的返回类型,不会因为实际调用的是子类实例就自动把返回类型改成ClassB。所以newBlockB03的实际类型是ClassA,自然没法直接传给需要ClassB的ClassC.AddBlock,就报了CS1503类型转换错误。
后来看到有人提到用“泛型魔法”解决,我查了下C#静态类型相关的资料,改出了下面这个泛型版本的代码:
namespace Demo { public class ClassA<T> where T : ClassA<T> { public T AddBlock() => (T)this; } public class ClassB : ClassA<ClassB>; public class ClassC { public ClassC AddBlock(ClassB classB) => this; } class Program { static void Main() { var classB = new ClassB(); var classC = new ClassC(); var newBlockB01 = classB; newBlockB01.AddBlock(); var newBlockB02 = (ClassB)classB.AddBlock(); var newBlockB03 = classB.AddBlock(); classC.AddBlock(newBlockB01); // Works as expected classC.AddBlock(newBlockB02); // Works as expected classC.AddBlock(newBlockB03); // Works as expected } } }
这里用了一个递归泛型约束的技巧——让父类ClassA<T>的泛型参数T必须是继承自ClassA<T>的类型。当子类ClassB继承ClassA<ClassB>时,就把自己绑定成了父类的泛型参数,这样父类里的AddBlock方法就能返回T也就是ClassB类型了。编译器这下就能正确推断返回值是ClassB,不用再手动强制转换,newBlockB03直接就能传给ClassC.AddBlock了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

