class A:
at offset 0 ... "abc" ... 4 byte int field
at offset 4 ... "xyz" ... 8 byte double field
at offset 12 ... "speak" ... 4 byte function pointer
class B:
at offset 0 ... "foo" ... 2 byte short field
at offset 2 ... 2 bytes of alignment padding
at offset 4 ... "bar" ... 4 byte array pointer
at offset 8 ... "baz" ... 4 byte function pointer
框架的设计目标之一,如 Java 和。NET 的目标是使编译后的代码能够与预编译库的一个版本一起工作,并与该库的后续版本同样良好地工作,即使后续版本添加了新的特性。虽然像 C 或 C + + 这样的语言中通常的范例是分发包含它们所需的所有库的静态链接的可执行文件,但是。NET 和 Java 将应用程序作为在运行时“链接”的组件集合分发。
之前的 COM 模型。NET 尝试使用这种通用的方法,但是它并没有真正的继承——相反,每个类定义都有效地定义了一个类和一个同名的接口,其中包含了所有的公共成员。实例属于类类型,而引用属于接口类型。将类声明为从另一个类派生的类等同于将类声明为实现另一个类的接口,并要求新类重新实现从其派生的类的所有公共成员。如果 Y 和 Z 派生自 X,然后 W 派生自 Y 和 Z,那么 Y 和 Z 是否以不同的方式实现 X 的成员并不重要,因为 Z 不能使用它们的实现——它必须定义自己的实现。W 可能封装 Y 和/或 Z 的实例,并通过它们的方法链接 X 的方法的实现,但是对于 X 的方法应该做什么没有模糊性——它们会做 Z 的代码明确指示它们做的任何事情。
class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z // Not actually permitted in C#
{
public static void Test()
{
var it = new W();
it.Foo();
}
}
看起来 W.Test()应该创建一个 W 实例来调用在 X中定义的虚方法 Foo的实现。然而,假设 Y 和 Z 实际上位于一个单独编译的模块中,尽管在编译 X 和 W 时它们被定义为上面的模块,但后来它们被更改和重新编译:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
现在,调用 W.Test()的效果应该是什么?如果程序在发布之前必须静态链接,静态链接阶段可能能够识别出,虽然在 Y 和 Z 被更改之前程序没有模糊性,但是对 Y 和 Z 的更改使事情变得模糊,链接器可以拒绝构建程序,除非或直到这种模糊性得到解决。另一方面,有可能同时拥有 W 和 Y 和 Z 的新版本的人只是想运行程序而没有任何源代码。当 W.Test()运行时,它将不再清楚 W.Test()应该做什么,但是直到用户尝试用 Y 和 Z 的新版本运行 W 时,系统的任何部分都不可能认识到存在问题(除非在对 Y 和 Z 进行更改之前 W 被认为是非法的)。