为什么C#不像C++/CLI那样直接在托管堆中修改字段?
Why C# does not modify fields directly in the managed heap like C++/CLI?
正如我在 Jeffrey Richter 撰写的 C# via CLR 一书中看到的,在谈到装箱和 unboxing/copying 操作时,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
}
为了改变装箱实例的值,我们需要先拆箱再装箱,性能不是很好。
为了避免我对这本书的误解,以下是作者的原话:
However, in some languages like C++/CLI, they allow you to unbox a
boxed value type without copying the fields.Unboxing returns the
address of the unboxed portion of a boxed object (ignoring the
object’s type object pointer and sync block index overhead). You can
now use this pointer to manipulate the unboxed instance’s fields
(which happen to be in a boxed object on the heap). For example, the
previous code would be much more efficient if written in C++/CLI,
because you could change the value of Point’s x field within the
already boxed Point instance. This would avoid both allocating a new
object on the heap and copying all of the fields twice!
我的问题来了:为什么 C# 不做 C++/CLR 做的事情?是否还有其他一些重要问题阻止 C# 团队进行此 "redundant" 设计并使性能不如我们预期的好?这种 "unnatural" 行为背后的原因是什么?
C# 和 C++ 的哲学有些不同。与作为系统编程语言的 C++ 不同,C# 想要使程序员免于处理内存分配和指针,因此它试图隐藏内存管理中涉及的一些细节。在 C# 中,值类型等同于 C++ 中的基本类型和结构。它们只是内存中某处的字节序列。在 C++ 中,您可以轻松地声明一个指向 int 或 struct 的指针,无论它位于何处,但 C# 不允许这样做。这种间接隐藏在 boxing/unboxing 机制中。
"Boxing" 大致意思就是在堆上创建值类型,默默返回一个指向这块内存的对象引用。拆箱只是颠倒了这种间接性。 Boxing/unboxing 每当您在需要引用类型的上下文中使用值类型时自动发生 - 例如,将整数转换为对象时:
Int32 i4Value = 12345;
Object pValue = (Object) i4Value;
将对象引用转换回整数会自动拆箱该值,即将其从堆中复制为正常值。
C++/CLI 团队做了很多让 C++ 程序员使用低级 C# 功能的魔法。这是一个例子。他们利用 C++ 语法让你做一些 C# 程序员甚至不知道的事情,因为它们发生在幕后的某个地方。
坦率地说,尽管我整天都在编写 C++/CLI 代码,但我从来不需要使用这种特殊的 boxing/unboxing C++/CLI 技巧。但是,肯定存在一些没有其他方法的情况 - 例如,当连接一些无法更改的奇怪的第 3 方 C# 组件时,因为没有可用的源代码。
正如我在 Jeffrey Richter 撰写的 C# via CLR 一书中看到的,在谈到装箱和 unboxing/copying 操作时,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
}
为了改变装箱实例的值,我们需要先拆箱再装箱,性能不是很好。
为了避免我对这本书的误解,以下是作者的原话:
However, in some languages like C++/CLI, they allow you to unbox a boxed value type without copying the fields.Unboxing returns the address of the unboxed portion of a boxed object (ignoring the object’s type object pointer and sync block index overhead). You can now use this pointer to manipulate the unboxed instance’s fields (which happen to be in a boxed object on the heap). For example, the previous code would be much more efficient if written in C++/CLI, because you could change the value of Point’s x field within the already boxed Point instance. This would avoid both allocating a new object on the heap and copying all of the fields twice!
我的问题来了:为什么 C# 不做 C++/CLR 做的事情?是否还有其他一些重要问题阻止 C# 团队进行此 "redundant" 设计并使性能不如我们预期的好?这种 "unnatural" 行为背后的原因是什么?
C# 和 C++ 的哲学有些不同。与作为系统编程语言的 C++ 不同,C# 想要使程序员免于处理内存分配和指针,因此它试图隐藏内存管理中涉及的一些细节。在 C# 中,值类型等同于 C++ 中的基本类型和结构。它们只是内存中某处的字节序列。在 C++ 中,您可以轻松地声明一个指向 int 或 struct 的指针,无论它位于何处,但 C# 不允许这样做。这种间接隐藏在 boxing/unboxing 机制中。
"Boxing" 大致意思就是在堆上创建值类型,默默返回一个指向这块内存的对象引用。拆箱只是颠倒了这种间接性。 Boxing/unboxing 每当您在需要引用类型的上下文中使用值类型时自动发生 - 例如,将整数转换为对象时:
Int32 i4Value = 12345;
Object pValue = (Object) i4Value;
将对象引用转换回整数会自动拆箱该值,即将其从堆中复制为正常值。
C++/CLI 团队做了很多让 C++ 程序员使用低级 C# 功能的魔法。这是一个例子。他们利用 C++ 语法让你做一些 C# 程序员甚至不知道的事情,因为它们发生在幕后的某个地方。
坦率地说,尽管我整天都在编写 C++/CLI 代码,但我从来不需要使用这种特殊的 boxing/unboxing C++/CLI 技巧。但是,肯定存在一些没有其他方法的情况 - 例如,当连接一些无法更改的奇怪的第 3 方 C# 组件时,因为没有可用的源代码。