我遇到了一个场景,其中一个值类型正在实现 ref。类型。
只是想知道这怎么可能(幕后发生了什么?)
结构是值类型,接口是 ref。类型,但结构可以实现接口而不会出现任何错误...
有什么想法吗?提前致谢
实际上,它同时以两种不同的方式进行。
首先,任何值类型都可以装箱到引用类型的对象实例中。这个盒子是由运行时按需发明的,它将以你期望的方式实现接口——即盒子将实现值类型实现的任何接口。
但是,CLI 也允许“受限调用”。受约束的调用将虚拟调用转换为静态调用,但仅适用于值类型通过覆盖或接口实现的方式实现实例方法的场景(否则它由 JIT 作为虚拟调用实现)。特别是泛型广泛使用了受约束的调用(正是由于这个原因,受约束的操作码与泛型同时添加)。
接口不是引用类型,它只是关于如何实现类型的约定。
接口类型的变量必须是引用,因为实现接口的类型可以是引用类型。接口类型的变量必须能够保存引用类型或值类型,因为两者都可以实现接口。可以将值类型装箱以便获得引用,但不能将引用类型“扁平化”为值。
尽管我们都使用 Type 这个词来指代接口,事实上,即使 MSDN 文档也将接口描述为引用 Types,但接口并不是与任何其他引用类型或任何值类型相同的类型。在非常真实的意义上,它根本不是一种类型。它是类型必须包含的行为(一组方法、属性和事件)的约定,该类型必须通过声明来实现该接口。
public interface ITestInterface { }
public class MyClass:ITestInterface { }
ITestInterface m = new MyClass() as ITestInterface;
var t = m.GetType();
你会看到即使变量m
被声明为类型ITestInterface
,类型变量t
仍然是MyClass
。
因此,尽管出于历史原因,我们使用单词类型来应用于接口,但接口的“类型”与具体对象的类型(类或结构的实例)是完全不同的。
引用 Don Box 的Essential .Net
CLR 处理对象和接口类型的方式与其前身(C++ 和 COM)不同。在 C++ 和 COM 中,给定的具体类型对于每个基本类型或支持的接口都有一个方法表。相反,CLR 中给定的具体类型只有一个方法表。通过推断,基于 CLR 的对象只有一个类型句柄。这与 C++ 和 COM 形成鲜明对比,其中一个对象通常每个基本类型或接口都有一个 vptr。因此,CLR
castclass
不会以与 C++dynamic_cast
或 COM相同的方式产生第二个指针值Query-Interface
。
读到这里很清楚,接口本身永远不可能有 vptr 表,或者CORINFO_CLASS_STRUCT
只有真正的具体对象(引用和值类型)才能拥有。此结构由 CLR 为执行代码在运行时加载的每个类型创建和维护。同样,来自Essential .Net,
CORINFO_CLASS_STRUCT
包含指向两个表的指针,这些表描述了该类型支持的所有接口。和[CLR] 操作码使用这些表之一isinst
来castclass
确定类型是否支持给定接口。这些表中的第二个是 CLR 在调度对基于接口的对象引用进行的虚拟方法调用时使用的接口偏移量表。
从这些引用中可以清楚地看出,接口与引用类型(类)或值类型(结构)根本不同。在 .Net 中实例化或使用的每个对象(引用类型)或值类型都必须是类或结构的具体实例。并且 CLR 加载的每个对象或结构都具有对为该类或结构的具体类型创建的单个 CORINFO_CLASS_STRUCT 的引用。接口是类型的类别,其定义是为了保证声明为该类别的任何类或结构(声明为实现该接口)必须包含每个声明的成员的类型成员(方法、属性、事件等)接口定义。
从某种意义上说,接口不是类和结构的类型!接口本身并不存在;所以它们既不是引用类型也不是值类型。您可以将 null 分配给引用。但是,如果实现者类型是值类型,那么当您将该类型的变量分配给另一种类型时,它的行为就像值类型一样。因此它的行为类似于引用类型的引用类型。
类和结构是数据(代码)的类型,但接口是类型的类型。它们可用于对其他类型进行分类。他们强迫其他类型遵循协议,仅此而已。它们只是一个布局,一个定义,一个契约;不是现有的东西。