Structs


Beginner Topic: Categories of Types

All types fall into two categories: reference types and value types. The differences between the types in each category stem from the fact that each category uses a different location in memory. To review, this Beginner Topic reintroduces the value type/reference type discussion to refamiliarize those who are unfamiliar with it.

Value Types

Value types directly contain their values, as shown in Figure 8.1. In other words, the variable refers to the same location in memory that the value is stored. Because of this, when a different variable is assigned the same value, a memory copy of the original variable's value is made to the location of the new variable. A second variable of the same value type cannot refer to the same location in memory as the first variable. So changing the value of the first variable will not affect the value in the second.

Figure 8.1. Value Types Contain the Data Directly


Similarly, passing a value type to a method such as Console.WriteLine() will also result in a memory copy, and any changes to the parameter value inside the method will not affect the original value within the calling function. Since value types require a memory copy, they generally should be defined to consume a small amount of memory (less than 16 bytes approximately).

The amount of memory that is required for the value type is fixed at compile time and will not change at runtime. This fixed size allows value types to be stored in the area of memory known as the stack.

Reference Types

In contrast, reference types and the variables that refer to them point to the data storage location (see Figure 8.2). Reference types store the reference (memory address) where the data is located, instead of the data directly. Therefore, to access the data, the runtime will read the memory location out of the variable and then jump to the location in memory that contains the data. The memory area of the data a reference type points to is the heap.

Figure 8.2. Reference Types Point to the Heap


Dereferencing a reference type to access its value involves an extra hop. However, a reference type does not require the same memory copy of the data that a value type does, resulting in circumstances when reference types are more efficient. When assigning one reference type variable to another reference type variable, only a memory copy of the address occurs, and as such, the memory copy required by a reference type is always the size of the address itself. (A 32-bit processor will copy 32 bits and a 64-bit processor will copy 64 bits, and so on.) Obviously, not copying the data would be faster than a value type's behavior if the data size is large.

Since reference types copy only the address of the data, two different variables can point to the same data, and changing the data through one variable will change the data for the other variable as well. This happens both for assignment and for method calls. Therefore, a method can affect the data of a reference type back at the caller.


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

      // Use keyword struct to declare a value type.     struct Angle                                                         {      public Angle(int hours, int minutes, int seconds)      {          _Hours = hours;          _Minutes = minutes;          _Seconds = seconds;      }      public int Hours      {          get { return _Hours; }      }      private int _Hours;      public int Minutes      {          get { return _Minutes; }      }      private int _Minutes;      public int Seconds      {          get { return _Seconds; }      }      private int _Seconds;      public Angle Move(int hours, int minutes, int seconds)      {          return new Angle(              Hours + hours,              Minutes + minutes.              Seconds + seconds)      } } // Declaring a class - a reference type // (declaring it as a struct would create a value type // larger than 16 bytes.) class Coordinate {    public Angle Longitude    {        get { return _Longitude; }        set { _Longitude = value; }    }    private Angle _Longitude;    public Angle Latitude    {        get { return _Latitude; }        set { _Latitude = value; }    }    private Angle _Latitude; }

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 structs

In 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

struct Angle {    // ...    // ERROR:    Fields cannot be initialized at declaration time    // int _Hours = 42;    // ... }

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.

Listing 8.3. Accessing Properties before Initializing All Fields

// ERROR:  The 'this' object cannot be used before //         all of its fields are assigned to // public Angle(int hours, int minutes, int seconds) // { //     Hours = hours;    // Shorthand for this.Hours = hours; //     Minutes = minutes // Shorthand for this.Minutes = ...; //     Seconds = seconds // Shorthand for this.Seconds = ...; // }

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.

Advanced Topic: Using new with Value Types

Invoking the new operator on a reference type compiles to the CIL instruction newobj. new is available to value types as well, but in contrast, the underlying CIL instruction is initobj. This instruction initializes the memory with default values (the equivalent of assigning default(<type>) in C# 2.0).


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.

Language Contrast: C++struct Defines Type with Public Members

In C++, the difference between structs and classes is simply that by default, a struct's members are public. C# doesn't include this subtle distinction. The contrast is far greater in C#, where struct significantly changes the memory behavior from that of a class.


Using the default Operator

To 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

 // Use keyword struct to declare a value type. struct Angle                                                      {   public Angle(int hours, int minutes)       : this( hours, minutes, default(int))   {   }   // ... }

Inheritance and Interfaces with Value Types

All 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().




Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net