Type Basics

A type defines the blueprint for a value. A value is a storage location denoted by a variable (if it can change) or a constant (if it cannot). We created a local variable named x in our first program.

All values in C# are an instance of a specific type. The meaning of a value, and the set of possible values a variable can have, is determined by its type. The type of x in our example program is int.

Predefined types (also called built-in types) are types that are specially supported by the compiler. The int type is a predefined type for representing the set of integers that fit into 32 bits of memory, from ‒231 to 231‒1. We can perform functions such as arithmetic with instances of the int type as follows:

int x = 12 * 30;

Another predefined C# type is string. The string type represents a sequence of characters, such as “.NET” or “http://oreilly.com”. We can work with strings by calling functions on them as follows:

string message = "Hello world";
string upperMessage = message.ToUpper();
Console.WriteLine (upperMessage);      // HELLO WORLD

int x = 2007;
message = message + x.ToString();
Console.WriteLine (message);         // Hello world2007

The predefined bool type has exactly two possible values: true and false. The bool type is commonly used to conditionally branch execution flow with an if statement. For example:

bool simpleVar = false;
if (simpleVar)
  Console.WriteLine ("This will not print");

int x = 5000;
bool lessThanAMile = x < 5280;
if (lessThanAMile)
  Console.WriteLine ("This will print");

Note

The System namespace in the .NET Framework contains many important types that are not predefined by C# (e.g., DateTime).

Just as we can build complex functions from simple functions, we can build complex types from primitive types. In this example, we will define a custom type named UnitConverter—a class that serves as a blueprint for unit conversions:

using System;

public class UnitConverter
{
  int ratio;                             // Field

  public UnitConverter (int unitRatio)   // Constructor
  {
    ratio = unitRatio;
  }

  public int Convert (int unit)          // Method
  {
    return unit * ratio;
  }
}

class Test
{
  static void Main()
  {
    UnitConverter feetToInches = new UnitConverter(12);
    UnitConverter milesToFeet = new UnitConverter(5280);

    Console.Write (feetToInches.Convert(30));   // 360
    Console.Write (feetToInches.Convert(100));  // 1200
    Console.Write (feetToInches.Convert
                    (milesToFeet.Convert(1)));  // 63360
  }
}

C# can convert between instances of compatible types. A conversion always creates a new value from an existing one. Conversions can be either implicit or explicit: implicit conversions happen automatically, whereas explicit conversions require a cast. In the following example, we implicitly convert an int to a long type (which has twice the bitwise capacity of an int) and explicitly cast an int to a short type (which has half the bitwise capacity of an int):

int x = 12345;       // int is a 32-bit integer
long y = x;          // Implicit conversion to 64-bit int
short z = (short)x;  // Explicit conversion to 16-bit int

In general, implicit conversions are allowed when the compiler can guarantee they will always succeed without loss of information. Otherwise, you must perform an explicit cast to convert between compatible types.

C# types can be divided into value types and reference types.

Value types comprise most built-in types (specifically, all numeric types, the char type, and the bool type) as well as custom struct and enum types. Reference types comprise all class, array, delegate, and interface types.

The fundamental difference between value types and reference types is how they are handled in memory.

The predefined types in C# are:

Predefined types in C# alias Framework types in the System namespace. There is only a syntactic difference between these two statements:

int i = 5;
System.Int32 i = 5;

The set of predefined value types, excluding decimal, are known as primitive types in the Common Language Runtime (CLR). Primitive types are so called because they are supported directly via instructions in compiled code, which usually translates to direct support on the underlying processor.