为啥.net(仍然)需要拳击?

Posted

技术标签:

【中文标题】为啥.net(仍然)需要拳击?【英文标题】:Why .net (still) needs boxing?为什么.net(仍然)需要拳击? 【发布时间】:2014-06-19 10:19:27 【问题描述】:

我一直在阅读 Eric Lippert 的博客,特别是关于堆、堆栈和寄存器的主题,据我所知,将变量放在堆或堆栈上的决定主要与变量的“生命周期”有关,即“短命”或“长命”,如果对堆栈上的变量进行任何操作,使其生命周期超过其声明的函数的生命周期,它将成为通过编译时包装类进行堆“提升”的候选者,就像在闭包中使用堆栈变量的情况一样。所以问题是为什么 .net 编译器(仍然)不识别需要装箱并选择实现一个类的候选者,这当然总是在堆上分配?反过来又完全取消拳击?

【问题讨论】:

我不明白您的建议将如何改进。此外,这并不是在所有情况下都可以在编译时检测到的。 ".. 并选择实现一个类," - 出于所有意图和目的,这将是拳击。 如果我可以换一种说法.. 在处理闭包时为什么不使用拳击?我并没有试图提出任何建议,我只是好奇为什么存在两种不同的方法来处理两个看似相似的场景,即闭包中的装箱与值类型。尽管 Brian 表示可能无法检测到变量是否会被装箱,但我仍然不清楚这种情况可能是什么。 您不能仅仅因为您需要对其中一些实例进行装箱,而对int 的所有实例进行装箱,并且您不能 装箱int 的任何实例仅仅因为您只需要装箱其中的几个。你有什么问题? 【参考方案1】:

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

【讨论】:

【参考方案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 中的一切都是通过对象完成的。一个共同的线程不会使这些操作相似到足以消除其中任何一个。如果您尝试将它们合并到一个操作中,您最终可能会得到一个效率极低的操作,该操作只是一直两件事 做,而很少需要这样做。

【讨论】:

感谢您抽出宝贵时间。 '对于即将离开范围但必须维护的任何数据类型、值或引用类型,必须执行此提升操作..'为我清除了它。现在看起来很明显,但让我很困惑。

以上是关于为啥.net(仍然)需要拳击?的主要内容,如果未能解决你的问题,请参考以下文章

为啥缓存的 Drools KIE 基础仍然需要源 .drl 文件?

如果存在 VLA,为啥仍然需要 malloc? [复制]

为啥在这个 angular2 示例中我仍然需要 @inject?

乘客是不是等同于 Heroku,为啥我仍然需要两者之一?

为啥 iOS ad-hoc 分发仍然需要信任企业开发者?

将 ES6 模块与 WebPack 一起使用,为啥仍然需要 require