为什么值类型继承自引用类型

本文关键字:引用类型 继承 类型 为什么 | 更新日期: 2023-09-27 18:28:11

>我有两个问题:

  1. 我们知道所有类型都派生自Object这是一个引用类型。我的问题是为什么int - 这是一种类型 - 继承自引用类型Object?这可能吗?

  2. 如果int是从Object派生的,为什么我们在将int传递给期望object作为参数的函数时需要装箱?通常,当您需要将派生类型的对象作为参数传递给期望基类型对象的函数时,对于引用,您无需执行任何其他操作。为什么在这里装箱?

对我来说,这种情况似乎是这种类型层次结构设计方式的问题。

附言。我发现了这个相关的问题,但那里的答案并没有给出任何现实的见解——只是抽象地谈论盒子。

为什么值类型继承自引用类型

我们需要注意不要在这里混淆概念。

首先是分型intobject的一个亚型。子类型基本上意味着由超类型保证的协定(例如,"有一个方法ToString,它返回一个合适的字符串选择">(也保证了子类型。

然后是 C# 中的继承。在 C# 中,继承

  1. 通过确保超类型提供的接口在子类型中也可用来创建子类型,并且

  2. 提供默认实现,即,如果不重写方法,则获得超类型的实现。这基本上是一个方便的功能。

(C# 中的接口实现将是另一种子类型机制的示例,该机制提供 1 而不是 2。

基本上就是这样。子类型或继承都不保证内存布局、值/引用类型语义等。这些概念是正交的。


"但这是不对的,">你可能会说。"object契约的一部分是'引用类型语义'。这就是需要拳击的地方。每当值类型的编译时类型是引用类型(即 objectValueType或接口(。

我们知道所有类型都派生自 Object,这是引用类型。我 问题是为什么 int - 这是值类型 - 从引用继承 类型对象?这可能吗?

System.Int32派生自System.ValueType,就像C#中的所有结构一样。编译器允许此继承链,这与禁止在任何其他struct类型中进行继承的机制相同。公共语言运行库 (CLR( 对于派生自 System.ValueType 的类型具有特殊的语义。 System.ValueType本身不是值类型,而是构成所有结构的基类的引用类型。虽然这个继承层次结构存在,但它不需要保证任何关于对象在内存中的布局。

为什么在将 int 传递给期望对象的函数时我们需要装箱 作为参数?通常带有引用,当您需要传递对象时 派生类型作为函数的参数,期望基类型的对象, 您无需执行任何其他操作。为什么在这里装箱?

因为尽管任何struct最终都派生自object,但运行时实际上对它的处理方式不同。所有结构都被视为一个数据 blob,它们没有方法表指针或同步块索引,而 .NET 中的每个引用类型都有这些指针或同步块索引。这就是为什么传递给接受object的方法的任何值类型都必须装箱的原因,因为需要向其中添加其他数据才能真正成为完全限定的object类型。值类型不仅在作为object类型传递时装箱,而且在装箱时,例如,当您调用由于实现接口而添加到struct的方法时。该值类型需要装箱,以便获取指向它需要调用的方法的实际方法表指针。

你可以用一个小例子看到它:

void Main()
{
    IFoo m = new M();
    m.X();
}
public struct M : IFoo
{
    public void X() { }
}
public interface IFoo 
{
    void X();
}

将生成以下 IL(在发布模式下编译(:

IL_0000:  ldloca.s    00 
IL_0002:  initobj     UserQuery.M
IL_0008:  ldloc.0     
IL_0009:  box         UserQuery.M
IL_000E:  callvirt    UserQuery+IFoo.X
IL_0013:  ret         

值类型要么是堆栈分配的,要么是在结构中内联分配的。引用类型是堆分配的。引用类型和值类型都派生自最终基类 Object。在值类型需要充当对象的情况下,将在堆上分配一个使值类型看起来像引用对象的包装器,并将值类型的值复制到堆中。标记包装器,以便系统知道它包含值类型。此过程称为装箱,反向过程称为拆箱。装箱和取消装箱允许将任何类型视为对象。