在.net运行时,每一个类型在创建第一个实例,或者静态成员被第一次访问,或者被反射创建时,就会创建一个与该类型关联的方法表:
基本结构大概如下:
+--------------------------+
| Method Table |
+--------------------------+
| Virtual Method #1 ptr |
| Virtual Method #2 ptr |
| ... |
| Non-virtual Method ptr |
+--------------------------+
| Type Information |
| (Size, Base Type ptr, |
| ... other info) |
+--------------------------+
其中重点分为两部分,方法表与描述类型的元数据。
方法表中的每一项包含了方法的标识信息与指针ptr,指针指向了方法体在内存中的实际地址。
方法表中的项又分为两类:虚方法与实方法,两者的区别体现在方法表的继承上。
举例子,我创建了如下两个类,他们存在继承关系:
cs
public class BaseClass
{
public virtual void MethodA()
{
Console.WriteLine("BaseClass.MethodA");
}
public virtual void MethodB()
{
Console.WriteLine("BaseClass.MethodB");
}
}
public class DerivedClass : BaseClass
{
public override void MethodB()
{
Console.WriteLine("DerivedClass.MethodB");
}
public virtual void MethodC()
{
Console.WriteLine("DerivedClass.MethodC");
}
}
两个类型的方法表如下:
基类BaseClass的方法表:
cs
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | 函数指针指向 --> BaseClass.MethodA
| Virtual Method #2 | 函数指针指向 --> BaseClass.MethodB
+------------------------+
子类DerivedClass的方法表:
cs
子类继承了基类的方法表项MethodA与MethodB
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | 未重写,所以依旧指向 --> BaseClass.MethodA
| Virtual Method #2 | 重写了,所以指向 --> DerivedClass.MethodB
| Virtual Method #3 | --> DerivedClass.MethodC
+------------------------+
基于这种机制,就实现了多态,在创建实例时,实例的"对象头"中包含了指向自身类型所对应的方法表的指针,基于此,找到方法表,再根据方法标识找到对应的方法表项,从而就自然而然找到了指向方法体的指针。
实例的"对象头"大概结构如下:
cs
+----------------------+
| 对象头 |
+----------------------+
| 类型指针 | --> 指向方法表的指针
| 同步锁信息 | --> 用于同步的信息
| 标记信息 | --> 垃圾回收标记信息
| ...其他元信息... |
+----------------------+
| 对象数据 | --> 实际存储对象数据的部分
+----------------------+
当将"实例"作为参数传递时,所传递的是"实例的引用",说人话就是传递指向上述结构中"对象头"部分起始位置的指针,找到了"对象头",运行时自然也就能够知道该对象的类型信息了,从而实现了高级的语言特性,如多态性、垃圾回收等。调用虚方法、判断对象的实际类型等操作都依赖于这种引用机制,使得对象在运行时能够灵活地适应不同的上下文。
抽象类
抽象类的方法表(Method Table)与普通类的方法表类似,不过抽象类的方法表中可能包含抽象方法。抽象方法是一种只有方法签名而没有具体实现的方法,需要在派生类中进行实现。
让我们通过一个简单的例子来理解抽象类的方法表。假设有如下的抽象类:
cs
public abstract class MyBaseClass
{
public abstract void AbstractMethod();
public void ConcreteMethod()
{
Console.WriteLine("Concrete method in MyBaseClass");
}
}
对应的抽象类方法表可能类似于以下结构:
cs
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | --> MyBaseClass.AbstractMethod
| Virtual Method #2 | --> MyBaseClass.ConcreteMethod
+------------------------+
在这个示例中:
AbstractMethod
是一个抽象方法,它在抽象类中只有方法签名而没有具体实现。ConcreteMethod
是一个普通的方法,它有具体的实现。
抽象方法在方法表中会被表示为虚方法(Virtual Method),因为它们需要在派生类中进行具体的实现。派生类中实现的具体方法会在其自己的方法表中添加相应的条目。
当一个派生类继承了抽象类并实现了抽象方法时,它的方法表可能会变成类似这样:
cs
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | --> DerivedClass.AbstractMethod (实现抽象方法)
| Virtual Method #2 | --> MyBaseClass.ConcreteMethod
+------------------------+
这样,抽象类的方法表中包含了抽象方法和具体方法的信息,而派生类的方法表中则包含了实现的具体方法的信息。这种继承关系和方法表的组织方式使得多态性在抽象类和派生类中得以实现。
接口Interface
接口(Interface)在.NET中也有与之相关的方法表(Method Table)的概念,不过与类的方法表有一些区别。接口中的方法表用于存储接口中定义的方法的签名,而不包含具体的实现。
让我们通过一个简单的例子来理解接口的方法表。假设有如下的接口:
cs
public interface IMyInterface
{
void InterfaceMethod();
}
对应的接口方法表可能类似于以下结构:
cs
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | --> IMyInterface.InterfaceMethod
+------------------------+
在这个示例中:
InterfaceMethod
是接口中定义的方法,它只有方法签名而没有具体实现。
当一个类实现了接口时,它会实现接口中定义的方法,并且在该类的方法表中会包含接口方法的实现。例如,如果有如下的类:
cs
public class MyClass : IMyInterface
{
public void InterfaceMethod()
{
Console.WriteLine("InterfaceMethod implementation in MyClass");
}
}
则 MyClass
的方法表可能会变成类似这样:
cs
+------------------------+
| Method Table |
+------------------------+
| Virtual Method #1 | --> MyClass.InterfaceMethod (实现接口方法)
+------------------------+
这样,接口的方法表记录了接口方法的签名,而实现了接口的类的方法表包含了实际的方法实现。这种方式支持了类对多个接口的实现,实现了接口隐式地引入了多态性,允许通过接口引用调用具体实现的方法。