The designers of the CTS were faced with the task of creating a type system in which everything is an object but the type system works in an efficient manner, when applicable. Their solution was to separate the CTS types into two categories: value types and reference types. These terms, as you'll soon see, reflect how variables are allocated and how they function internally.
When you have a variable that is a value type, you have a variable that contains actual data. Thus, the first rule of value types is that they cannot be null. For example, below I have allocated memory by creating a variable of the CTS type System.Int32 in C#. In this declaration, what happens is that a 32-bit space is allocated on the stack.
int i = 32;
In addition, the act of assigning a value to i results in that 32-bit value being moved into this allocated space.
There are several value types defined in C#, including enumerators, structures, and primitives. Anytime you declare a variable of one of these types, you have allocated the number of bytes associated with that type on the stack and are working directly with that allocated array of bits. In addition, when you pass a variable that is a value type, you're passing that variable's value and not a reference to its underlying object.
Reference types are similar to references in C++ in that they are type-safe pointers. The type-safe part means that instead of merely being an address, which might or might not point to what you think it does, a reference (when not null) is always guaranteed to point to an object that is of the type specified and that has already been allocated on the heap. Also take note of the fact that a reference can be null.
In the following example, a reference type (string) is being allocated. However, what's happening under the covers is that the value has been allocated on the heap and a reference to that value has been returned.
string s = "Hello, World";
As with value types, there are several types defined as reference types in C#: classes, arrays, delegates, and interfaces. Any time you declare a variable of one of these types, you allocate the number of bytes associated with that type on the heap, and you are working with a reference to that object instead of directly with the bits (as with value types).