C# data types fall into two main categories – Value Types and Reference Types. (C# also supports Pointer Types in unsafe contexts, but we shall skip that at the moment.)
Value types consist of two main categories – Structs and Enumerations. The entire Value Type hierarcy looks like this:
User defined structs
All value types are allocated on the stack. Once a value type falls out of scope, it is automatically removed from the memory. When you assign one value type to another, required memory is allocated on the stack, and a member-by-membery copy (also called shallow copy) is performed to set the new item to correct (same) state. To make deep copies, use a class. Remember that C# does not have support for Copy constructor, so when you use a class, you need code an explicit method (say Clone()) to make a deep copy. Also note that System.Object does provide a protected method called ‘MemberwiseClone()’ that makes shallow copies.
The base class for all value types is System.ValueType. System.ValueType is an abstract class with a protected constructor. It inherits from System.Object and overrides the virtual methods. All structs implicity inherit from System.ValueType. You may think of a struct as a light weight class. Here is some interesting information about structs:
1) Say you have a struct “Fruit”. You may declare the a variable as “Fruit a;” or as “Fruit a = new Fruit();”. Both are equivalent, and memory is allocated on stack in each case.
2) Structs can contain methods, but these methods cannot be virtual. Afterall, you cannot inherit a struct, so the ability for polymorphic behavior would be futile. However, you CAN override certain methods of the base classes (System.ValueType, System.Object).
3) Structs can contain member fields. These fields may be value types or reference types. Structs may contain static (class-scope) members as well.
4) .NET provides a default constructor behind the scenes for each struct. This constructor initializes all members to their default values. You cannot provide your own implementation of this default constructor – .NET complains that ‘Structs cannot contain explicit parameterless constructors.‘
5) You can provide custom constructors to user defined structs, but while doing so, you need to initialize all member fields of that struct (value type and reference type). If you don’t .NET complains ‘Field YourStruct.field must be fully assigned before control leaves the constructor‘. The only way of using a custrom constructor would be with the ‘new’ keyword, like ‘Fruit a = new Fruit(“Banana”);”.
Allright, while we are talking about value types, here is a fun question – can you guess what happens to the memory when the following code is executed?
int a = 1; // Line 1
object o = a; // Line 2
int b = (int) o; // Line 3
Basically, the first line allocates some memory on stack for integer a and intitializes it to value 1. Line 2 starts the fun. Since object does not inherit from System.ValueType, .NET allocates memory in heap and copies the value from a (1 in this case) to the memory (called Boxing). Line 3 allocates memory on stack for b and initializes it to the same value as refered by a (called Unboxing).
Lets talk about enumerations now. All enumerations inherit from System.Enum, which in turn inherits from System.ValueType. System.Enum also implements interfaces IComparable, IFormattable and IConvertible.
By default, the storage used for each item in an enumation is Int32. You may change this to any other integral type except a char. Following code demonstrates how to do this:
enum MyFruits : byte
Although you may seldom need to do this, you can use the GetUnderlyingType() method of System.Enum to get the data type used to store its values. You can use the static GetValues() method of System.Enum to obtain its items as name-value pairs as an array.