Home > C# .NET > C# .NET Tutorial 2: Reference Types

C# .NET Tutorial 2: Reference Types

Hello there welcome to the second tutorial on C#. If you haven’t read the first tutorial then I suggest that you read it. So let’s start straight away.

Reference types provide a lot of flexibility and offer better performance when they are passed to methods. But what is really a reference type? Unlike value-types reference types store an address of the data (aka pointer) on the stack. The actual data will be found at that address on heap. The heap is managed at runtime and items on the heap that are not needed anymore are removed by the garbage collector.

To better understand let us compare reference-type variables with value-type variables.

The first difference is this. Because reference-type variables only contain the address of memory where the data is stored if you assign this variable to another variable you will have another variable pointing to the same address in memory. Therefore these two variables are pointing to the same data on heap. This implies that if you change the value of one variable the value of the second variable will be changed automatically because they are both pointing to the same thing! Let us see through an example:

NumberClass c1 = new NumberClass(4);
Console.WriteLine(c1.Value); //This will display 4.

NumberClass c2 = c1;
Console.WriteLine(c2.Value); //This will display 4.

c1.Value += 1;
Console.WriteLine(c1.Value); //This will display 5.

Console.WriteLine(c2.Value); //This will display 5 because both c1 and c2 point to the same thing.

Built-In Reference Types

In .NET everything that is not derived from System.ValueType is a reference type. So as you can imagine there are a lot. Here I list the six most common (and the most you will use):

  • System.Object: This is the most general. You can convert any type to System.Object. Because of this any type in .NET will have the ToString, GetType, and Equals members inherited from this type.
  • System.String: used to store text.
  • System.Array: this keeps an array of data and is the base class of all arrays.
  • System.Text.StringBuilder: used to store dynamic text data.
  • System.Exception: it handles system and application-defined exceptions. Any exception (even user defined) inherits from this.
  • System.IO.Stream: This is an abstract base class and it defines a buffer for file, network I/O, device I/O, etc.

In the next sections we look at each of these built-in references.

Strings and String Builders

The System.String provides a number of members that can be used to manipulate text. For example to search and replace in a string you use the following:

string s = "This blog contains C sharp tutorials";
s = s.replace("sharp", "#");

So what happens when the contents of a string change? Strings in .NET are immutable. When a string changes a new string is created and the old one is discarded. This is invisible to the programmer however it is important that one knows because creating extra strings means the garbage collector needs to spend more time collected unreferenced (not used) strings. Consider this example:

string s = "This";
s += " is";
s += " a";
s += " blog";

This example has caused 3 temporary strings, which the garbage collector has to remove during runtime. Avoiding these temporary strings will improve performance. To avoid temporary strings:

  • Use the String class’ Concat, Join, and Format methods, and
  • Use the StringBuilder to create dynamic (mutable) strings.

With the StringBuilder you can specify the size of the string. The default created by the constructor is 16 bytes long. You can specify an initial size and the maximum size. For example:

System.Text.StringBuilder s = new System.Text.StringBuilder(30) // 30 bytes long
s.Append("This ");
s.Append("is a ");
s.Append("blog");
string message = s.ToString(); //convert to a string

The String class overrides operators from System.Object. These are:

  • Addition (+) – concatenates two strings.
  • Equality (==) – returns true if content of two strings are the same else it returns false.
  • Inequality (!=) – the opposite of equality.
  • Assignment (=) – copies the contents of one string to another (acts like value types even though they are reference types).

How to create and sort Arrays

As with the string type the System.Array provides members that can be used to work on the data present in the array. In C# arrays are declared using square brackets []. For example in the code below we will create an array and sort it:

//create a int array
int[] arr = {22,1,85,19,29,0,5};
//sort the array by calling a static method
Array.sort(arr);

Using Streams

Streams can be used to read and write to disk and communicating over a network. For whatever reason you use a stream all streams are derived from System.IO.Stream. Network streams can be found in System.Network.Sockets, and encrypted streams can be found in System.Security.Cryptography. The following are the most common streams that you will use:

  • FileStream – used to write or read, to or from files.
  • MemoryStream – used to write or read, to or from memory.
  • StreamReader – reads from a stream.
  • StreamWriter – writes to a stream.

The StreamReader and the StreamWriter accept the path of a text file as a parameter in the constructor. The following code, which needs the System.IO namespace, shows how to read and write to a text file:

//write to a text file
StreamWriter sw = new StreamWriter("blog.txt");
sw.WriteLine("C# tutorial!");
sw.Close();

//read from a text file
StreamReader sr = new StreamReader("blog.txt");
//display on screen
Console.WriteLine(sr.ReadToEnd());
sr.Close();

Later on in this series of tutorials I will dedicate a number of tutorials solely for I/O.

Exceptions

Exceptions are events that occur unexpectedly. For example a typical exception is when an application is trying to perform a mathematical division and the divisor is 0 like 4/0. Another typical example that will cause an exception is when an applilcation is trying to read from a file that does not exist on disk.

A good programmer will anticipate such exceptions and thus handle them in his/her code so that the application does not stop abruptly. In the following code we handle an exception that may occur while reading from a file (this code requires System.IO namespace):

try
{
    StreamReader sr = new StreamReader("test.txt");
    Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
    Console.WriteLine("Error reading file");
}

If any type of error occurs while reading the file the code goes in the catch block. If no error occurs the code will not go in the catch block. In this particular example any error that may occur will be caught. This is because we used the Exception class which is the base of all possible exception. Other more specific exceptions exist all deriving from System.SystemException. You can create your own more specific exceptions by deriving them from System.ApplicationException. One can also have multiple exception handlers to do different things according to the excption that occured. Here is an example that displays different error messages if a file is not found, if the user does not have the sufficient privileges, and any other type of error:

try
{
    StreamReader sr = new StreamReader("test.txt");
    Console.WriteLine(sr.ReadToEnd());
}
catch (System.IO.FileNotFoundException ex)
{
    Console.WriteLine("File not found!");
}
catch (System.UnauthorizedAccessException ex)
{
    Console.WriteLine("You do not have sufficient privileges to open this file!");
}
catch (Exception ex)
{
    Console.WriteLine("An error occurred while reading from file!");
}

While handling exceptions one can also include a finally section with the try catch block. The finally block is always executed irrespective whether an exception has occurred or not. Therefore the finally block should be used to do some clean up after your code. For example in the previous examples we were opening a stream but never closing it. We should close the stream in the finally block. This means that whether data is read successfully from the stream or not we will close the stream at the end:

//Stream declaration was moved outside so that it is available to both the try and finally blocks
StreamReader sr;

try
{
    sr = new StreamReader("test.txt");
    Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
    Console.WriteLine("Error reading from file!");
}
finally
{
    sr.Close(); //stream is closed
}

Typically all code except for variable declarations should occur between try..catch blocks. Exception handling improves the user experience and will even help you while debugging your code. However always keep in mind that it incurs a slight performance penalty.

Hope you liked this second tutorial. Please leave comments if you have any queries or if you want to change something. Until then see you for the next tutorial: Creating Classes. Bye Bye!


Categories: C# .NET