Every definition of a type derived from ValueType actually defines two distinct kinds of things in the runtime: a type of heap object (which derives from ValueType
and in turn from Object
, and which contains embedded type information), and a type of storage location (which doesn't contain any embedded type information, but instead requires that code which uses it must have some other means of knowing what it is). An instance of the heap object type contains a field of the storage-location type, and code which attempts to access this
will access that field. If a value type is cast implicitly or explicitly to a storage location of reference type, the system will create a new heap object with the appropriate type and copy all public and private fields of the value-type to corresponding fields within the heap object. If a heap object is cast to a value-type storage location, all public and private fields from the heap object will be copied to the value-type storage location.
If an attempt is made to use any Object
or interface method on a heap object of a value type, the method will be called just like any other heap-object method. If the attempt is made on a value-type storage location, for any method other than GetType
, the compiler will generate a special "constrained" opcode which informs the runtime of the type of the storage location and instructs the Runtime to call the address of the method appropriate to that type. Since the compiler will have the storage location type available, and the run-time can use that to find the appropriate method, the appropriate method can be invoked directly on the storage location, without having to create a new heap object first. GetType
is the one notable exception; since it works by examining the type information embedded within an object, it can only work on things that have embedded type information. Consequently, its argument will be converted to the heap-object form before the call; GetType
will then be able to examine the embedded type information of that heap object.