如果你看一下类(而不是它们的实例),那么我宁愿这样画:

这意味着Dog
类通常比类具有更多的方法和属性Animal
(例如,一只狗可以bark
(方法)和四个legs
(属性))。当然,在实例化这个类时必须保留额外的内存。想象一下,首先创建基类方法和属性,然后在内存中创建派生方法和属性:
class Dog : Animal
{
public Dog()
{
legs = 4;
Console.WriteLine("Dog constructor");
}
public int legs { get; private set; }
public void bark()
{
Console.WriteLine("grrrwoof!");
}
}
如果您实例化 aDog
并将其分配给Animal
引用变量,那么此引用只能访问 aAnimal
具有的方法。尽管如此,整个Dog
对象仍然保存在内存中:
Dog d = new Dog();
Animal a = (Animal)d;
换句话说,d
能够做到以下几点:
Console.WriteLine(String.Format("Number of legs: {0}", d.legs.ToString()));
d.bark();
但a
不能这样做,因为这些“特性”没有在Animal
类中定义。
现在重要的是要知道并非所有类型的演员表都是允许的。始终允许从 aDog
转换为 an Animal
,因为这是安全的,但您不能将 an隐式Animal
转换为 a Dog
,因此以下代码会引发无效的转换异常:
Dog dogRef2 = a; // not allowed
如果您知道自己在做什么(即,如果您确定a
包含 的实例Dog
),那么您可以显式转换如下:
Dog dogRef2 = (Dog)a; // allowed
之后您可以访问属性和方法:
dogRef2.bark(); // works
这是可行的,因为编译器和运行时总是以相同的结构化方式将方法和属性存储在内存中,并且还会创建一个内部描述符以在引用它时找到它。
请注意,这并不总是安全的,例如,如果您尝试以下操作:
Animal a = new Animal();
Dog dogRef2 = (Dog)a; // Invalid cast exception
为什么?因为new Animal()
还没有创建方法bark
和属性legs
,它只是创建了一个Animal
(既不包含属性legs
也不包含方法bark
)的实例。
更多信息:如果您想了解有关内部结构(如何创建和存储对象)的更多信息,请查看此链接。这是一个内存布局的例子,取自那里:

您可以看到链表用于构建从基类实例对象到派生类实例对象的链。