In contrast to classes, value types (see Partition I, section 8.2.4) are not accessed by using a reference but are stored directly in the location of that type. RATIONALE Value types are used to describe the type of small data items. They can be compared to struct (as opposed to pointers to struct) types in C++. Compared to reference types, value types are accessed faster, since there is no additional indirection involved. As elements of arrays they do not require allocating memory for the pointers as well as for the data itself. Typical value types are complex numbers, geometric points, or dates. Like other types, value types may have fields (static or instance), methods (static, instance, or virtual), properties, events, and nested types. A value type may be converted into a corresponding reference type (its boxed form, a class automatically created for this purpose by the VES when a value type is defined) by a process called boxing. A boxed value type may be converted back into its value type representation, the unboxed form, by a process called unboxing. Value types shall be sealed, and they shall have a base type of either System.ValueType or System.Enum (see the .NET Framework Standard Library Annotated Reference). Value types shall implement zero or more interfaces, but this has meaning only in their boxed form (see Partition II, section 12.3). Unboxed value types are not considered subtypes of another type, and it is not valid to use the isinst instruction (see Partition III [section 4.6]) on unboxed value types. The isinst instruction may be used for boxed value types. Unboxed value types shall not be assigned the value null and they shall not be compared to null. Value types support layout control in the same way as reference types do (see Partition II, section 9.7). This is especially important when values are imported from native code. 12.1 Referencing Value TypesThe unboxed form of a value type shall be referred to by using the valuetype keyword followed by a type reference. The boxed form of a value type shall be referred to by using the boxed keyword followed by a type reference.
12.2 Initializing Value TypesLike classes, value types may have both instance constructors (see Partition II, section 9.5.1) and type initializers (see Partition II, section 9.5.3). Unlike classes that are automatically initialized to null, however, the following rules constitute the only guarantee about the initialization of (unboxed) value types:
RATIONALE Guaranteeing automatic initialization of unboxed value types is both difficult and expensive, especially on platforms that support thread-local storage and allow threads to be created outside of the CLI and then passed to the CLI for management. NOTE Boxed value types are classes and follow the rules for classes. The instruction initobj (see Partition III [section 4.5]) performs zero-initialization under program control. If a value type has a constructor, an instance of its unboxed type can be created as is done with classes. The newobj instruction (see Partition III [section 4.20]) is used along with the initializer and its parameters to allocate and initialize the instance. The instance of the value type will be allocated on the stack. The Base Class Library provides the method System.Array.Initialize (see the .NET Framework Standard Library Annotated Reference) to zero all instances in an array of unboxed value types. Example (informative): The following code declares and initializes three value type variables. The first variable is zero-initialized, the second is initialized by calling an instance constructor, and the third by creating the object on the stack and storing it into the local. .assembly Test { } .assembly extern System.Drawing { .ver 1:0:3102:0 .publickeytoken = (b03f5f7f11d50a3a) } .method public static void Start() { .maxstack 3 .entrypoint .locals init (valuetype [System.Drawing]System.Drawing.Size Zero, valuetype [System.Drawing]System.Drawing.Size Init, valuetype [System.Drawing]System.Drawing.Size Store) // Zero-initialize the local named Zero ldloca Zero // load address of local variable initobj valuetype [System.Drawing]System.Drawing.Size // Call the initializer on the local named Init ldloca Init // load address of local variable ldc.i4 425 // load argument 1 (width) ldc.i4 300 // load argument 2 (height) call instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) // Create a new instance on the stack and store into Store. Note that // stobj is used here but one could equally well use stloc, stfld, etc. ldloca Store ldc.i4 425 // load argument 1 (width) ldc.i4 300 // load argument 2 (height) newobj instance void [System.Drawing]System.Drawing.Size::.ctor(int32, int32) stobj valuetype [System.Drawing]System.Drawing.Size ret } 12.3 Methods of Value TypesValue types may have static, instance, and virtual methods. Static methods of value types are defined and called the same way as static methods of class types. As with classes, both instance and virtual methods of a boxed or unboxed value type may be called using the call instruction. The callvirt instruction shall not be used with unboxed value types (see Partition I, section 8.4.2), but it may be used on boxed value types.
Instance and virtual methods of classes shall be coded to expect a reference to an instance of the class as the this pointer. By contrast, instance and virtual methods of value types shall be coded to expect a managed pointer (see Partition I, section 8.2.4) to an unboxed instance of the value type. The CLI shall convert a boxed value type into a managed pointer to the unboxed value type when a boxed value type is passed as the this pointer to a virtual method whose implementation is provided by the unboxed value type. NOTE This operation is the same as unboxing the instance, since the unbox instruction (see Partition III [section 4.30]) is defined to return a managed pointer to the value type that shares memory with the original boxed instance. The following diagrams may help understand the relationship between the boxed and unboxed representations of a value type. RATIONALE An important use of instance methods on value types is to change internal state of the instance. This cannot be done if an instance of the unboxed value type is used for the this pointer, since it would be operating on a copy of the value, not the original value: unboxed value types are copied when they are passed as arguments. Virtual methods are used to allow multiple types to share implementation code, and this requires that all classes that implement the virtual method share a common representation defined by the class that first introduces the method. Since value types can (and in the Base Class Library do) implement interfaces and virtual methods defined on System.Object, it is important that the virtual method be callable using a boxed value type so it can be manipulated as would any other type that implements the interface. This leads to the requirement that the VES automatically unbox value types on virtual calls. [Table 3-2 shows the type of the this pointer supplied, depending on the type of the instance method, and whether the call or callvirt instruction is used.]
Example (informative): The following converts an integer of the value type int32 into a string. Recall that int32 corresponds to the unboxed value type System.Int32 defined in the Base Class Library. Suppose the integer is declared as: .locals init (int32 x) Then the call is made as shown below: ldloca x // load managed pointer to local variable call instance string valuetype [mscorlib]System.Int32::ToString() However, if System.Object (a class) is used as the type reference rather than System.Int32 (a value type), the value of x shall be boxed before the call is made, and the code becomes: ldloc x box valuetype [mscorlib]System.Int32 callvirt instance string [mscorlib]System.Object::ToString() |