1.对象的创建过程
Bird bird创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象,分配内存空间和初始化操作,然后将这个对象引用赋给bird变量,也就是建立bird变量与Bird对象的关联。
2.从继承的角度分析对象的创建过程
在此我们以Chicken对象的创建为例,首先是字段,对象一经创建,会首先找到其父类Bird,并为其字段分配存储空间,而Bird也会继续找到其父类Animal,为其分配存储空间,依次类推直到递归结束,也就是完成System.Object内存分配为止。
思考
通过上面的讲述与分析,我们基本上对.NET在编译期的实现原理有了大致的了解,但是还有以下的问题,一定会引起一定的疑惑,那就是: Bird bird2 = new Chicken();
这种情况下,bird2.ShowType应该返回什么值呢?而bird2.type又该是什么值呢?有两个原则,是.NET专门用于解决这一问题的:
关注对象原则:调用子类还是父类的方法,取决于创建的对象是子类对象还是父类对象,而不是它的引用类型。例如Bird bird2 = new Chicken()时,我们关注的是其创建对象为Chicken类型,因此子类将继承父类的字段和方法,或者覆写父类的虚方法,而不用关注bird2的引用类型是否为Bird。引用类型不同的区别决定了不同的对象在方法表中不同的访问权限。
注意:根据关注对象原则,那么下面的两种情况又该如何区别呢?
Bird bird = new Chicken();
Chicken chicken = new Chicken ();
根据我们上文的分析,bird对象和chicken对象在内存布局上是一样的,差别就在于其引用指针的类型不同:bird为Bird类型指针,而chicken为Chicken类型指针。以方法调用为例,不同的类型指针在虚拟方法表中有不同的附加信息作为标志来区别其访问的地址区域,称为offset。不同类型的指针只能在其特定地址区域内进行执行,子类覆盖父类时会保证其访问地址区域的一致性,从而解决了不同的类型访问具有不同的访问权限问题。
执行就近原则:对于同名字段或者方法,编译器是按照其顺序查找来引用的,也就是首先访问离它创建最近的字段或者方法,例如上例中的bird,是Bird类型,因此会首先访问Bird_type(注意编译器是不会重新命名的,在此是为区分起见),如果type类型设为public,则在此将返回“Bird”值。这也就是为什么在对象创建时必须将字段按顺序排列,而父类要先于子类编译的原因了。
//经典指令解析之方法调度
using System;
using System.Collections.Generic;
using System.Text;
namespace testCall
{
public class Father
{
public void DoWork()
{
Console.WriteLine("Father.DoWork()");
}
public virtual void DoVirtualWork()
{
Console.WriteLine("Father.DoVirtualWork()");
}
public virtual void DoVirtualAll()
{
Console.WriteLine("Father.DoVirtualAll()");
}
}
public class Son : Father
{
public static void DoStaticWork()
{
Console.WriteLine("Son.DoStaticWork()");
}
public new void DoWork()
{
Console.WriteLine("Son.DoWork()");
}
public new virtual void DoVirtualWork()
{
base.DoVirtualWork();
Console.WriteLine("Son.DoVirtualWork()");
}
public override void DoVirtualAll()
{
Console.WriteLine("Son.DoVirtualAll()");
}
}
public class Grandson : Son
{
public override void DoVirtualWork()
{
base.DoVirtualWork();
Console.WriteLine("Grandson.DoVirtualWork()");
}
public override void DoVirtualAll()
{
base.DoVirtualAll();
Console.WriteLine("Grandson.DoVirtualAll()");
}
}
//方法调用测试类
class call
{
static void Main(string[] args)
{
Father son = new Son();
son.DoWork();//调用Father.DoWork()
son.DoVirtualWork();//调用Father.DoVirtualWork()
Son.DoStaticWork();//调用Son类特有的静态方法DoStaticWork()
Father aGrandson = new Grandson();
aGrandson.DoWork();//调用Father.DoWork()
aGrandson.DoVirtualWork();//调用Father.DoVirtualWork()
aGrandson.DoVirtualAll();//调用Grandson.DoVirtualAll()
Console.ReadKey();
}
}
}
new隐藏基类成员中同名的成员方法,也就是该方法独立于基类的方法。派生类同名方法如果未定义为new或override则默认定义为new的方法。
override表示覆写,显式的重写基类成员,以实现派生类自己的版本。C++派生类中对同名的基类方法的覆写只需将基类方法声明为virtual。
Son中定义的new DoWork() 独立于Father中定义的同名的DoWork()方法,对于Son对象继承的DoWork()方法被new隐藏而不可见。而override DoVirtualAll()则生成自己版本的DoVirtualAll()方法。
根据以上示意图很容易分析出以上测试代码的结果。
说明:
本文摘自《你必须知道的.NET》第十五回《继承本质论》,有改动。
http://www.cnblogs.com/anytao/archive/2007/09/10/must_net_15.html