为什么.net(还)需要拳击?

时间:2014-05-02 16:43:42

标签: c# .net stack clr boxing

我一直在阅读Eric Lippert的博客,特别是关于堆,堆栈和寄存器的主题,以及我可以理解的将变量放在堆或堆栈上的决定主要与变量的'生命周期'有关,即'short lived'或'long lived'如果对堆栈中的变量做了任何事情,使其生命周期超过其声明的函数的生命周期,它将成为通过编译时包装器类进行堆'促销'的候选者,就像在闭包中使用的堆栈变量一样。所以问题是为什么.net编译器(仍然)不识别需要装箱的候选者并选择实现一个类,当然这将永远在堆上分配?而反过来完全取消拳击?

2 个答案:

答案 0 :(得分:2)

我将回答您在评论中提出的问题,我认为这是另一种说法的同一问题,因为我认为这有助于消除您的困惑。

  

我只是好奇为什么存在两种不同的方式来处理两种看似相似的场景,即拳击与封闭中的值类型。

你在这里谈论了两个操作,"拳击"和"解除",他们完成两件完全不同的事情。这是一个实现细节,他们碰巧通过类似的方式做这些事情,但他们解决了单独的问题,并有不同的要求。

装箱的目的是允许将值类型存储为引用类型并在以后提取。它与所讨论的变量的范围无关,而与维护类型安全性有关。拳击可以完全在变量的局部范围内发生,例如:

int i = 1;
object o = i;
int j = (int)o;

但是,当需要将值类型传递给期望引用类型的参数时,更常使用它,例如:

string.Format("The value is {0}", 10);

string.Format采用params object[]参数,因此传递给方法的每个值类型都被加框。在CLR的类型系统中,所有值类型都继承自System.Object,因此将值类型视为对象始终是安全的操作。另一方面,取消装箱操作依赖于开发人员从正确的方框中取消正确的东西,这种验证只能在运行时进行,因为编译器无法确定什么是"真实"存储在这些对象中的值没有在编译时。

另一个操作,即提升,用于改变标识符的默认生存期,该标识符通常遵循它的词法范围。必须对任何数据类型,值或引用类型执行此提升操作,这些数据类型,值或引用类型即将保留范围但必须保持(例如,它们由lambda关闭)。这样做不是为了改变数据类型的表示,而是为了确保在方法返回后值可用,并防止它垃圾收集现在无法访问的引用实例。

请注意"解除"值类型未加框。编译器创建一个表示整个闭包的类,其中包括任何已关闭的值类型标识符的值类型成员。这些值类型永远不会被推送到object并在以后退出,而不仅仅是您自己的值类型字段。

您似乎专注于以下事实:这两个操作都是通过创建一个类的新实例来实现的,该实例包含"包含"盒装或提升型。但这真的不应该让你感到惊讶。 .NET中的一切是通过对象完成的。一个共同的线程不会使这些操作足够相似以消除其中任何一个。如果你试图将它们合并到一个操作中,你可能最终会得到一个非常低效的操作,它只是一直这两个的东西,当很少需要它时。

答案 1 :(得分:1)

在你给出的例子中 - 闭包是在编译时处理的,而装箱是在运行时进行的,但在这两个例子中,你都在提升变量的生命周期。