Fundamental Types of C#

One important thing you must understand about data types in C# is that it divides these data types into two fundamental categories – value types and reference types. Most of the basic data types in C# (for example int, char) are value types. Structures are also value types. Reference types include classes, interfaces, arrays and strings. The basic idea is simple, a value type represents the actual data (stored on the stack), whereas a reference type represents a pointer or reference to the data (stored on the heap). C# determines which types will be values and which ones will be represented as references.

The basic difference between value and reference types is in the way they are stored in the memory. Value types just hold a value in the memory. These types are stored on the stack. Value type is the way C and C++ works when passing a variable to a function, without any specific modifiers. Primitive data types, enums, structs and even objects would fall under this category
for C++ but in C# only primitive data types, enums and structs are treated as Value types.

As for reference types, the memory location of a reference type just contains the address of the object on the heap. If a reference type is null it means that no object has been referenced.

Let us consider the following example.

Example 11:

using System;
class Test
{
   static void Main()
   {
      int value = 10;
      funcTest(value);
      Console.WriteLine(value);
   }

   static void funcTest(int value)
   {
      value = 200;
   }
}

The output of the program would be 10 and not 200 as the value of the variable (value) being changed in the function (funcTest()) is not reflected back in the Main() function. This happens because int is a value type and when passed to the function funcTest() a copy of the value of the variable is passed.

Now consider Example 12.

Example 12:

using System;
class TestClass
{
   public int value;
}
class TestRef
{
   static void Main()
   {
      TestClass x = new TestClass();
      x.value = 10;
      funcTest(x);
      Console.WriteLine(x.value);
   }
   static void funcTest(TestClass x)
   {
      x.value = 200;
   }
}

The output of the program now will be 200. Why did that happen? Simple, the parameter passed to the function this time was an object. And object belongs to the reference type, so when the object was passed the reference (also known as address) to the object was passed and the changes made inside the function were reflected back to the Main() function.

In below Table we have summarized the common characteristics of value and reference types.

Value Reference
Variable Holds Actual Value Reference
Allocated Inline (Stack) Heap
Default Value Zeroed Null
= means Copy Value Copy Reference

As the table depicts:

  • Variables of value type hold the actual value while the reference types hold the address (a reference) of the object.
  • Value types are allocated inline while reference type on the heap.
  • As mentioned in a previously, C# allocates a default value for the variables. The default value for a Value is zeroed while a Reference holds a null reference.
  • Performing the “=” operation on value types copies the value to the destination variable while performing the same operation on reference types copies the reference (also known as address) of the object to the destination.

Boxing and Unboxing

Boxing in simple terms is nothing but conversion of a value type into a reference type. Similarly, unboxing is all about converting a reference type into a value type. This is a very powerful feature of C#. With boxing you can manipulate complex reference types as you would manipulate simple value types. This plays an important role in implementing polymorphism, which we shall discuss in a later post in detail.

To understand boxing ad unboxing clearly, let us consider the program in Example 13:

Example 13:

Example13

In Example 13 we have defined a class, which has two methods. The first method, iTakeObjs() takes in an object as a parameter. The second method iSpitObjs() returns an object, and it takes no parameters. In the implementation part of the code we have declared a variable called v, which is of type int and hence a value type. We assign 5 to v. We also create a new object up of the class BoxMe.

The next two lines are of importance to us. In the line commented BookMark1 we have called the iTakeObjs() method and passed v as the parameter. Recall that iTakeObjs() takes in an Object as parameter. An object is of reference type and we have passed v (a value type) as the parameter. Generally you would think that this would generate an error. However, it does not! The value type v gets implicitly converted into a reference type. This process of converting a value type to a reference type (either explicitly or implicitly, as in this case) is known as Boxing.

Now in the next line marked BookMark2 we have created a new variable of type int called unBox. We have then equated this variable to the iSpitObjs() method. Recall that iSpitObjs() takes in no parameters but returns an object. But we have equated it to a variable of type int, which is of value type. For this reason we have to explicitly cast the object to an int (the type conversion is done by enclosing the desired data type in parenthesis). This process of converting a reference type to a value type is known as Unboxing.

However note that here Unboxing required an explicit cast. The runtime performs a check to make sure that the specified value type matches the type contained in the reference type. If the check fails, an exception is thrown, which if not taken care of will terminate the application. We shall learn how to trap exceptions in a later post.

Leave a comment