问题描述
classA{publicA(){Say();}publicvirtualvoidSay(){Console.WriteLine("A的虚函数");}}classB:A{privateintnumOne=3;privateintnumTwo;publicB(){Console.WriteLine("B构造函数");}publicoverridevoidSay(){Console.WriteLine("NumOne={0},NumTwo={1}",numOne,numTwo);}}classProgram{staticvoidMain(string[]args){BmyB=newB();Console.Read();}}
楼主这里有两个问题:1.实例化子类B,首先要调用A类的构造函数,这里A的构造函数会调用到A的虚函数Say(),为什么不执行A的虚函数体,而会跑去执行B的重写overrideSay()啊?2.在执行overrideSay()的时候,不是还没有调用B的构造函数么,那岂不是还没有给实例化B类在堆上分配内存?那那两个privatenum1,和num2,的值是怎么保存起来的啊?他们又不是静态变量..
解决方案
解决方案二:
1、虚函数是找这个实例所在类的实现,如果没有才再找父类中的实现2、实例化是先分配内存,再调用构造函数,你可以感受下下面的代码classA{protectedinti=1;publicA(){Say();}publicvirtualvoidSay(){}}classB:A{publicB(){i=2;}publicoverridevoidSay(){Console.WriteLine("i="+i);}}
解决方案三:
B对say函数进行了重写,所以B调用构造函数时,调用了B的构造函数,大多数情况下,虚函数一般是没有方法体的,即什么都不做,具体操作留给继续类来实现。类初始化的时候会先分配内存,在构造函数执行前变量已经分配了内存
解决方案四:
在构造方法中调用虚方法本来就是一件危险的事情,这个时候子类构造尚未调用,如果子类重写的虚方法想当然的认为类成员应该已经初始化过,就可能出问题。所以一般设计时应该避免在构造中调用虚方法,如果无法避免,那么虚方法必须仔细实现,并给继承者有文档说明。
解决方案五:
引用3楼github_22161131的回复:
在构造方法中调用虚方法本来就是一件危险的事情,这个时候子类构造尚未调用,如果子类重写的虚方法想当然的认为类成员应该已经初始化过,就可能出问题。所以一般设计时应该避免在构造中调用虚方法,如果无法避免,那么虚方法必须仔细实现,并给继承者有文档说明。
没听懂。。
解决方案六:
比如你写了一个抽象的写入器Writer类,封装在一个程序集中给别人用,这个类如下:abstractclassWriter{protectedWriter(){Write(0);}publicabstractvoidWrite(intval);}
如果没有说明,别人并不知道它会在构造中就调用Write方法,于是他这样子写了一个子类:classListWriter:Writer{List<int>_list;publicListWriter(){_list=newList<int>();}publicoverridevoidWrite(intval){_list.Add(val);}}
看起来没有问题,结果newListWriter();就会抛出NullReferenceException,准备去揍Writer类的作者。改成下面的样子可以解决问题:classListWriter:Writer{List<int>_list=newList<int>();publicoverridevoidWrite(intval){_list.Add(val);}}
不过这并不适用于所有情况(字段初始化会优先于构造运行,但只能是简单的表达式,无法初始化复杂对象)。所以Writer类的设计是失败的,需要根据情况修改。