拳击 vs ValueType 参考;有什么区别?
最近有几个关于将 ValueType 装箱为对象的问题,特别是它是否在某些情况下发生。
我意识到我不知道的是,“装箱”ValueType(将其视为引用的对象)和简单地通过引用访问它之间有什么区别,例如使用 ref 或 out 关键字(您传递的只是“指针”)?在这两种情况下,该值都位于您可以指向它的某个位置(对于对象来说,它是堆,对于本地范围的 ValueType 来说,它是......到底在哪里?)。
如果我不得不猜测,根据我对 C++ 的了解,我会说它是这样工作的:通过引用访问的 ValueType(假设通过参数关键字)保留在其作用域的调用堆栈级别上,但是“创建了指向堆栈中该变量存储桶的“快捷方式”指针,并成为堆栈下一层的一部分。因为该值已经存储在内存中(甚至可能是 CPU 缓存),所以您不必在堆上实例化新的东西;唯一的新东西是指针,它是它自己的 ValueType (一个 IntPtr),并且本身存储在堆栈中,因此据我所知,它比将某些内容放入堆中更快。
这是正在发生的事情,还是还有其他事情正在发生?
编辑:更清楚:
public void TakesAnObject(Object obj) {...}
public void TakesAnIntValueType(ref int myValue) {...}
public void AnotherIntParameterMethod(out int myValue) {...}
...
//this locally-scoped variable is simply created on the stack.
int myInt = 5;
//Performs boxing; an Object is instantiated in the heap that holds the
//variable value from the stack, and that is passed by ref.
TakesAnObject(myInt);
//Apparently does NOT perform boxing, but we're still dealing with a reference.
//So what's going on?
TakesAnIntValueType(myInt);
//Again created on the stack, with the default 0.
int anotherInt;
//Again, apparently no boxing, but we're dealing with a reference to anotherInt.
AnotherIntParameterMethod(anotherInt);
There were a couple recent questions about boxing a ValueType as an Object, in particular whether it happened in certain instances.
Something I realized I don't know is, what is the difference between "boxing" a ValueType (treating it as a referenced Object) and simply accessing it by reference, for instance using ref or out keywords (where what you are passing is just a "pointer")? In both cases, the value is somewhere where you can point to it (for an Object it's the heap, for a locally-scoped ValueType it's... where, exactly?).
If I had to guess, from what I know of C++, I would say it works like this: a ValueType accessed by reference (let's say via parameter keyword) remains on the level of the call stack for which it is scoped, but a "shortcut" pointer to that variable's bucket in the stack is created and becomes part of the next layer of the stack. Because the value is already stored in memory (probably even the CPU cache), you don't have to instantiate something new on the heap; the only new thing is the pointer, which is its own ValueType (an IntPtr) and is itself stored on the stack, so AFAIK it would be faster than putting something in the heap.
Is this what's going on, or is there something else afoot?
EDIT: More clarity:
public void TakesAnObject(Object obj) {...}
public void TakesAnIntValueType(ref int myValue) {...}
public void AnotherIntParameterMethod(out int myValue) {...}
...
//this locally-scoped variable is simply created on the stack.
int myInt = 5;
//Performs boxing; an Object is instantiated in the heap that holds the
//variable value from the stack, and that is passed by ref.
TakesAnObject(myInt);
//Apparently does NOT perform boxing, but we're still dealing with a reference.
//So what's going on?
TakesAnIntValueType(myInt);
//Again created on the stack, with the default 0.
int anotherInt;
//Again, apparently no boxing, but we're dealing with a reference to anotherInt.
AnotherIntParameterMethod(anotherInt);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
类引用可以自由复制,并允许无限期存在。人们可以将它们视为对象的标识符,这些对象(始终)作为独立项目存储在堆上。为了避免过度使用术语“引用”,我喜欢将它们视为 ObjectID。
当例程通过引用接受参数时,该引用是普通类系统之外的特殊类型的事物(我将其称为 ParameterReference)。与可以无限期存在的 ObjectID 不同,ParameterReference 只允许在被调用函数的持续时间内存在。此外,与始终保存对独立对象的引用的 ObjectID 不同,ParameterReference 保存对堆栈上的局部变量、类对象中的字段、数组中的项目或结构中的字段的引用,该结构本身与这些描述之一匹配。如果 ParameterReference 指向局部变量,则该变量一旦超出范围将不再存在;在此之后尝试使用 ParameterReference 可能会导致数据损坏。然而,由于变量的范围保证至少会扩展到被调用的例程退出为止,并且由于那时 ParameterReference 将不再存在,因此 ParameterReference 访问不再存在的变量不会有危险。
Class references may be freely copied, and are allowed to exist indefinitely. One can think of them as identifiers for objects which are stored (always) as stand-alone items on the heap. To avoid overusing the term "reference", I like to think of them as ObjectIDs.
When a routine accepts a parameter by reference, that reference is a special type of thing which is outside the normal class system (I'll call it a ParameterReference). Unlike an ObjectID, which can exist indefinitely, a ParameterReference is only allowed to exist for the duration of the called function. Further, unlike an ObjectID which always holds a reference to a standalone object, a ParameterReference holds a reference to either a local variable on the stack, a field within a class Object, an item within an array, or a field within a struct which itself matches one of these descriptions. If the ParameterReference points to a local variable, that variable will cease to exist once it goes out of scope; attempting to use the ParameterReference after that time could cause data corruption. Since the scope of the variable is guaranteed to extend at least until the called routine exits, however, and since the ParameterReference will cease to exist at that time, there's no danger of a ParameterReference accessing a variable that no longer exists.
TakesAnObject(myInt);
必须装箱,因为它是作为对象类型(引用类型)接收的。如果目标是值类型,则它将被复制。
TakesAnIntValueType(ref myInt);
是对与 myInt 相同的内存区域的引用,因此如果更改“两者都会更改”。如果不是 ref,则该值将被复制。
TakesAnObject(myInt);
has to box because it is received as an object type (reference type). If the target was a value type it would had been copied instead.
TakesAnIntValueType(ref myInt);
is a reference to the same memory area as myInt, so if changed "both change". If it was not ref the value would had been copied instead.
你很接近了。当值类型被装箱时,它被复制到位于 GC 堆上的对象结构中。对象结构具有对象的常见前导码,其后的位是 blitted 值类型结构。当它被拆箱时,这个结构被重新复制到堆栈上。
You're close. When the value type is boxed, it is copied to an object structure which lives on the GC heap. The object structure has the usual preamble for objects, and the bits which come after this are the blitted value type structure. When it is unboxed, this structure is recopied onto the stack.