因为这里探讨的涉及到编译器和内存分配,可能有些地方说的不对,希望高手能不吝赐教!
对象实例化
首先看看下面代码:
public class A { public A() { print(); } public virtual void print() {} } public class B:A { public int x=1,y=2; public B() { Console.WriteLine("xx"); y=3; } public override void print() { Console.WriteLine("x={0},y={1}",x,y); } }
然后用 B bb = new B(); 创建一个对象
执行的结果:x=1,y=2
xx
很多人会认为这个结果是很自然的,不过也会有人会思考:当子类用new初始化的时候会调用基类的构造在构造了执行的方法应该是基类的,相当与一个基类变量调用他的一个方法。现在我们慢慢看实例化B的过程(可以添加相应的变量、方法跟踪调试),b实例化时,先给B独有的变量分配空间,然后执行构造方法,在执行B构造方法代码之前去给在A里继承的变量分配内存空间,调用基类的构造方法,然后调用自己的构造函数。我们知道子类继承父类的方法,就像他们是在子类里声明的一样,然而构造方法是不能继承的,C#默认是调用基类的无参构造函数(java有时候需要基类里加上没参数的构造函数)。
C#子类调用基类构造方法相当与把基类无参数构造方法的代码copy到子类的构造方法里的说法是错误的。子类调用基类构造方法是为了实例化该子类在基类继承来的字段、方法,虽然调用的是基类定义的方法但真正的执行调用的类或者说是运行时的类型是子类,这里也就是类B。在看看虚方法的调用,msdn上这样写:调用虚方法时,将为重写成员检查该对象的运行时类型(这里的重写成员值的是虚方法/属性等)。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。所以上班print()的运行类型是B而B也重写了print()方法,这样就解释了结果。
实际写代码的时候应避免在构造方法中调用虚方法。因为可能会设计到一些子类没有实例化的字段引发异常。
类的声明
看下面这段程序
public class C { public void cost() { Console.WriteLine("ccc"); } } public class D:C { public D() {} public new void cost() { Console.WriteLine("ddd"); } }
然后执行:C d=new D();d.cost();
结果:ccc
new的作用是使D中的方法独立于C的方法,即C的cost被覆盖(隐藏)了。其实new一个方法也是不经常用的,除非有必要一般不用。
变量的声明就是告诉编译器这个变量的类型,当变量是一个类的时候无论你是用哪个类去实例化,调用给类的方法时候总是以声明的变量为准的。也许有人说这个时候还没有C的实例呢,对虽然这时没有C 的实例不过C的cost方法代码的内存入口地址已经在d实例化的时候分给了d,这人人很难理解我的环境是win XP、Microsoft .NET Framework 2.0.50727不知道跟这个有没有关系。特殊的情况,当声明的类的方法是abstract或者是virsual的时候他们会到实例化的类的字节码文件去找相应的方法。当方法是非虚方法的时候就直接执行了。
*在C#中一切方法如果没有显示指明,都是非虚的。对于非虚的方法,CLR运行的时候并不会进行类型检查,而是直接运行该引用的类型中所定义的方法,即使这个引用所指向的实际类型是该引用类型的派生类,并且在派生类中存在着同名同参的方法,也不会运行派生类中定义的方法。这时,派生类中的方法隐藏了基类中的方法。(早绑定(early binding))
看下类的声明周期:
具体到类的变量和方法的内存分配:
当程序执行时,类的字节码文件被加载到内存,如果该类没有创建对象,类的实例变量不会被分配内存,类的静态变量(类变量)会分配内存。当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址,当该类的第一个对象实例化时才分配;类方法在该类加载时就分配相应的入口地址。