为啥.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 文件?