Jan. 16, 2007, 6:42 a.m.
posted by tekkero
Structs
Besides string and object, all the C# primitive types are value types. Furthermore, numerous additional value types are provided within the framework. It also is possible for developers to define their own value types that behave like user-defined primitives. To define a custom value type, you use the same type of structure as you would to define classes and interfaces. The key difference in syntax is simply that value types use the keyword struct, as shown in Listing 8.1. Listing 8.1. Defining struct
This listing defines Angle as a value type that stores the hours, minutes, and seconds of an angle, either longitude or latitude. The resulting C# type is a struct. Note Although nothing in the language requires it, a good guideline is for value types to be immutable: Once you have instantiated a value type, you should not be able to modify the same instance. In scenarios where modification is desirable, you should create a new instance. Listing 8.1 supplies a Move() method that doesn't modify the instance of Angle but instead returns an entirely new instance. Initializing structsIn addition to properties and fields, structs may contain methods and constructors. However, default (parameterless) constructors are not allowed. Sometimes (for instance, when instantiating an array) a value type's constructor will not be called because all array memory is initialized with zeroes instead. To avoid the inconsistency of default constructors being called only sometimes, C# prevents explicit definition of default constructors altogether. Because the compiler's implementation of an instance field assignment at declaration time is to place the assignment into the type's constructor, C# prevents instance field assignment at declaration time as well (see Listing 8.2). Listing 8.2. Initializing a struct Field within a Declaration, Resulting in an Error
This does not eliminate the need to initialize the field. In fact, for structs that are accessible only from within the assembly (structs decorated with internal or private modifiers), a warning is reported if the field fails to be initialized after instantiation. Fortunately, C# supports constructors with parameters and they come with an interesting initialization requirement. They must initialize all fields within the struct. Failure to do so causes a compile error. The constructor in Listing 8.3 that initializes the property rather than the field, for example, produces a compile error. Accessing Properties before Initializing All Fields
The error reports that methods and properties (Hours implies this.Hours) are accessed prior to the initialization of all fields. To resolve the issue, you need to initialize the fields directly, as demonstrated in Listing 8.1.
Unlike classes, structs do not support finalizers. For local variable value types, memory is allocated on the stack, so there is no need for the garbage collector to handle the value type's cleanup and no finalizer is called before the stack is unwound. For value types that are part of a reference type, the data is stored on the heap and memory is cleaned up as part of the reference object's garbage collection.
Using the default OperatorTo provide a constructor that didn't require _Seconds would not avoid the requirement that _Seconds still required initialization. You can assign the default value of _Seconds using 0 explicitly or, in C# 2.0, using the default operator. Listing 8.4 passes the default value into the Angle constructor that includes _Seconds. However, the default operator can be used outside of the this constructor call (_Seconds = default(int), for example). It is a way to specify the value for the default of a particular type. Listing 8.4. Using the default Operator to Retrieve the Default Value of a Type
Inheritance and Interfaces with Value TypesAll value types are sealed. In addition, all value types derive from System.ValueType. This means that the inheritance chain for structs is always from object to ValueType to the struct. Value types can implement interfaces, too. Many of those built into the framework implement interfaces such as IComparable and IFormattable. ValueType brings with it the behavior of value types, but it does not include any additional members (all of its members override object's virtual members). However, as with classes, you can override the virtual members of System.Object. The rules for overriding are virtually the same as with reference types (see Chapter 9). However, one difference is that with value types, the default implementation for GetHashCode() is to forward the call to the first non-null field within the struct. Also, Equals() makes significant use of reflection. This leads to the conclusion that if a value type is frequently used inside collections, especially dictionary-type collections that use hash codes, the value type should include overrides for both Equals() and GetHashCode(). |
- Comment