【问题标题】:Why C# does not modify fields directly in the managed heap like C++/CLI?为什么 C# 不像 C++/CLI 那样直接在托管堆中修改字段?
【发布时间】:2017-08-01 09:23:32
【问题描述】:

正如我在 Jeffrey Richter 所写的C# via CLR 一书中看到的,在谈到装箱和拆箱/复制操作时,Richter 先生展示了一个演示:

public static void Main() {
    Point p; // Point is a value type defined before
    p.x = p.y = 1;
    Object o = p; // Boxes p; o refers to the boxed instance

    // Change Point's x field to 2
    p = (Point) o; // Unboxes o AND copies fields from boxed
    // instance to stack variable
    p.x = 2; // Changes the state of the stack variable
    o = p; // Boxes p; o refers to a new boxed instance
}

为了改变一个装箱实例的值,我们需要做一个unboxing-then-boxing-again的操作,性能不是那么好。

为了避免我对书的误解,这正是作者所说的:

但是,在 C++/CLI 等某些语言中,它们允许您将 装箱值类型而不复制字段。取消装箱返回 已装箱对象的未装箱部分的地址(忽略 对象的类型对象指针和同步块索引开销)。你可以 现在使用这个指针来操作未装箱实例的字段 (恰好在堆上的一个装箱对象中)。例如, 如果用 C++/CLI 编写,以前的代码会更有效率, 因为您可以在 已经装箱的 Point 实例。这将避免同时分配一个新的 堆上的对象并复制所有字段两次!

我的问题来了:为什么 C# 不像 C++/CLR 那样做?是否还有其他一些重要问题阻止了 C# 团队进行这种“冗余”设计并导致性能不如我们预期?这种“不自然”行为的背后是什么?

【问题讨论】:

  • 如果您在类中使用Point 作为字段,它将在堆上。装箱和拆箱是一个不好的例子,无论它是否在堆上。当您将 valuetype 值存储在引用类型中时,就完成了装箱。我认为这与堆栈中的值类型有关。当你装箱时,它会将引用 (pointer) 存储在堆栈上,并将数据存储在堆上。
  • @JeroenvanLangen 首先,我认为您误解了我的问题;其次,装箱会将数据保留在堆栈上并复制它,而不仅仅是“指针”。
  • 实际上,将 C++/CLI 行为称为“不自然”同样有意义。在 C# 中,最佳实践是将值类型设计和使用为不可变的,这样就不会出现改变它们的整个“效率”问题。当您不需要甚至不想要它时,没有理由添加可能很复杂的功能。使用这样的功能是否可以保持内存安全?
  • @HenkHolterman Emm... 我认为您的回答可能是正确的。但是您能否提供更详细的信息,例如给出此功能如何影响内存安全的具体示例?谢谢。
  • C++/CLI 的语法允许您引用一个typed boxSystem::Int32^System::Int32 的一个装箱实例。 C# 缺少这样的语法,所以它唯一能让你做的就是在拆箱时复制值。顺便说一句,这不是什么大问题,如果您使用拳击,您已经输掉了表演之战。

标签: c# c++-cli clr boxing


【解决方案1】:

C# 和 C++ 有一些不同的理念。与作为系统编程语言的 C++ 不同,C# 想要使程序员免于处理内存分配和指针,因此它试图隐藏一些涉及内存管理的细节。在 C# 中,值类型等同于 C++ 中的原始类型和结构。它们只是内存中某处的字节序列。在 C++ 中,您可以轻松地声明一个指向 int 或 struct 的指针,无论它位于何处,但 C# 不允许这样做。这种间接性隐藏在装箱/拆箱机制中。

“装箱”大致意思是在堆上创建值类型,并默默返回对这块内存的对象引用。拆箱只是反转了这种间接性。每当您在需要引用类型的上下文中使用值类型时,装箱/拆箱就会自动发生 - 例如,将整数转换为对象时:

Int32 i4Value = 12345;
Object pValue = (Object) i4Value;

将 Object 引用转换回整数会自动将值拆箱,即将其从堆复制到正常值。

C++/CLI 团队做了很多奇事,让 C++ 程序员可以使用低级 C# 功能。这是一个例子。它们利用 C++ 语法让你做一些 C# 程序员甚至不知道的事情,因为它们发生在幕后的某个地方。

坦率地说,尽管我整天都在编写 C++/CLI 代码,但我从来不需要使用这种特殊的 C++/CLI 装箱/拆箱技巧。但是,在某些情况下肯定没有其他方法 - 例如,当连接一些无法更改的奇怪的 3rd-party C# 组件时,因为没有可用的源代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-09
    • 2018-02-04
    • 2023-03-11
    相关资源
    最近更新 更多