我有两个问题:
我们知道所有类型都来自Object
,它是一种引用类型。我的问题是为什么int
- 值类型 - 继承自引用类型Object
?这可能吗?
如果int
来自Object
,为什么我们需要在将int
传递给期望object
作为参数的函数时打包?通常使用引用,当您需要将派生类型的对象作为参数传递给函数期望基类型的对象时,您不需要做任何额外的操作。为何选择这里?
对我来说,这种情况似乎是这种类型层次结构的设计问题。
PS。我找到了this相关的问题,但那里的答案并没有给出任何真实的见解 - 只是抽象地谈论盒子。
答案 0 :(得分:3)
我们需要注意不要在这里混淆概念。
首先,子类型。 int
是object
的子类型。子类型基本上意味着由超类型保证的合同(例如“有一个方法ToString,它返回一个合适的字符串重复表示。”)也保证了子类型。
然后在C#中有继承。在C#中,继承
通过确保子类型提供的接口在子类型中也可用来创建子类型,并且
提供默认实现,即,如果不覆盖方法,则会获得超类型的实现。这基本上是一个方便的功能。
( C#中的接口实现将是另一个子类型机制的示例,它提供1而不是2。)
基本上都是这样。子类型或继承都不保证内存布局,值/引用类型语义等。概念是正交的。
“但那不对,”你可能会说。 “object
合同的一部分是'引用类型语义'。”这是需要装箱的地方。只要值类型的编译时类型是引用类型(即object
,ValueType
或接口),它就会模拟引用类型语义。
答案 1 :(得分:2)
我们知道所有类型都来自Object。这是参考类型。我的 问题是为什么int--它是值类型 - 继承自引用 类型对象?这可能吗?
System.Int32
派生自System.ValueType
,C#中的所有结构也是如此。编译器允许使用此继承链,这与禁止您在任何其他struct
类型中继承的机制相同。公共语言运行库(CLR)对从System.ValueType
派生的类型具有特殊语义。 System.ValueType
本身并不是一个值类型,它是一个引用类型,它构成了所有结构的基类。虽然存在这种继承层次结构,但它无需保证对象如何在内存中布局。
为什么我们需要在将int传递给期望对象的函数时使用 作为参数?通常在需要传递对象时使用引用 派生类型作为函数期望基类型对象的参数, 你不需要做任何额外的事情。为什么要在这里装?
因为尽管任何struct
最终都来自object
,但实际上它在运行时受到不同的处理。所有结构都被视为一个数据块,它们没有方法表指针或同步块索引,这是.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
答案 2 :(得分:-1)
值类型是堆栈分配或在结构中内联分配。引用类型是堆分配的。引用和值类型都是从最终基类Object派生的。 如果值类型需要像对象一样运行,则在堆上分配使值类型看起来像引用对象的包装器,并将值类型的值复制到它。标记包装器,以便系统知道它包含值类型。此过程称为装箱,反向过程称为拆箱。装箱和拆箱允许将任何类型视为对象。