C# Types of Variables

As C# is a strictly object-oriented language, everything in C# is in a class and in some sense everything is thus a type or an aspect of a type.


Value and Reference Types of Variables

A variable of a value type directly contains a value, rather than a reference to a value stored somewhere. Value types include enumerations, built-in data types (numeric types, char and bool), and user-defined structs.

It is not possible to derive a new type from a value type.

Value types are local variables; they are destroyed when they go out of scope. They exist on the stack (as local variables for the current method call).

A reference data type does not directly store a value. It stores a "reference" which contains the location of the value which is stored elsewhere on the memory heap. There could be several references to the same value. If at some point, there is no longer a reference to the value, then it will be marked for removal and at some point the garbage-collection system will remove it.

Reference data types are:


Built-in Data Types

All of the above are "integral" types. We also have:

To specify the type of a numerical literal value, append a letter:

     float X = 3.5F;  // F for float

Likewise we have D for double, U for uint or ulong and UL (or LU) for specifically ulong.

In many cases (but not all), there are predefined implicit conversions.


enum

The keyword "enum" is used to declare an "enumeration", a user-defined type that consists of a set of named constants called the "enumerator list".

Usually an enumeration is placed in a namespace, but it could be nested inside a class or struct.

Enumeration members are always public.

An enumeration has an underlying type which is any integral type except char. The default is int. The items in the list have integer values which start at 0 (by default). Both defaults can be overridden.

Examples:

     enum Seasons {Spring, Summer, Autumn, Winter};

Here Spring has the value 0, Summer has the value 1, etc., stored as type int.

     enum Seasons : byte {Spring, Summer, Autumn, Winter};

Now the values are stored as type byte.

     enum Seasons {Spring = 1, Summer, Autumn, Winter};

Now Spring has the value 1, Summer has the value 2, etc.

End of Examples

To refer to one of the values in an enumeration, prefix it with the name of the enumeration, as in Season.Spring.

A variable of an enumerated type can be assigned any value of the underlying type, even if it is not in the appropriate range (a bad idea).

An variable of an enumerated type can be explicitly cast as an integral type.


struct

A struct is a value type similar to a class but with some restrictions. (See class.)

A struct is a value type, while a class is a reference type.

Typically a struct is used to contain data fields. Like a class, a struct can contain various other members (constructors, methods, properties, etc). The syntax looks like this:

     access-modifier struct struct-name
     {
      ...
     }

A struct cannot inherit from another struct and its members cannot be declared as protected.

A struct can implement an interface.


object

All types derive directly or indirectly from an class called Object, and the term "object" is an alias for Object. A variable of type object is in some sense a variable of an unspecified type. A variable of a value type can be converted (implicitly) to type object; this is called "boxing". That specific variable can later be "unboxed", converting its value back to the other type.

A variable of type object is allocated on the heap. Value types are not normally on the heap; this is a way to put a value type on the heap.

Example:

     int I = 123;      // Here int is a value type.
     object O = I;     // This is boxing.  The variable O now knows it contains
                       // an int, as we could verify with sizeof or GetType().
     int J = (int) O;  // This is unboxing.  It works because O was boxed earlier.


const

This is used in declaring a field or variable to make it constant, as in

     const int LIMIT = 12;

or in a class definition

     public const int L = 17;

A const field or variable can be initialized only when it is declared.


readonly

This is used in declaring a field to make it constant, but the field can be initialized either when it is declared or by a constructor in the same class.

Example:

     readonly int M = 20;

or (taken from an MSDN page)

     // (used in a constructor)
     public static readonly uint TimeStamp = (uint) DateTime.Now.Ticks;

(Thus a readonly field may have its value determined at run time.)


Nullable Types

In the System namespace we have a struct named Nullable<T>. The parameter T may be any value type. A nullable type can represent any of the values of its underlying type, as well as "null". (Reference types already inherently support null as a possible value.) Do not think of null as 0 or an empty string; we do know or need to know how it is stored internally.

Example: a Nullable<byte> variable can be assigned any value from 0 to 255, or it can be assigned the value null.

In general, try to avoid trying to do any operations with the value null. The result is (predictably) often null. (This can be frustrating.)

The syntax T? is shorthand for Nullable<T>, so

     int? X;

and

     Nullable<int> X;

mean the same thing.

The Nullable<T> struct has properties including:

The operator ?? can be used to provide a default value that is returned when a nullable type is assigned to a non-nullable type. For example:

     int? c = null;
     int d = c ?? -1;
     // Now d = c, unless c is null, in which case d = -1.

Nullable types are especially useful in database work to represent unknown data or data that does not apply. For instance, a table might list "First Name", "Last Name" and "Middle Initial", but some people do not have middle initials, so the value null could be used. (Likewise some people use only one name.)