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 referenc开发者_如何学Pythone (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);
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);
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.
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.
精彩评论