Click here to Skip to main content
Click here to Skip to main content

Reference and Value Types in C#

, 24 Dec 2008
Rate this:
Please Sign up or sign in to vote.
An article to help the beginner understand C# classes and structures.

Introduction

This paper is meant for the beginner. It will begin with the standardized differences between reference types and value types, and conclude with a brief explanation of using I/O to access information.

The Common Language Runtime is the virtual execution system responsible for running all managed code. It is an implementation of the CLI—and therefore the Common Type System (CTS). The CTS types fall into two broad categories: reference types and value types. Reference types are known as classes, and value types are known as structures. An instance of the reference type, called an object, is allocated and managed on the Garbage Collection heap, and all reads, writes, and sharing of it are performed through a reference (i.e., a pointer indirection). A value type instance, called a value, on the other hand, is allocated inline as a sequence of bytes, the location of which is based on the scope in which it is defined (e.g., on the execution stack if defined as local, on the GC heap if contained within a heap-allocated data structure).

Creating User-Defined Types

User–defined types are also called structures, or structs, after the language keyword used to create them. As with other value types, instances of user-defined types are stored on the stack, and they contain their data directly. The following code defines a structure using the struct keyword, to create a type that cycles through a set of integers between minimum and maximum values set by the constructor:

using System;
using System.Drawing;
struct Cycle
{
    int _val, _min, _max;
    public Cycle(int min, int max)
    {
        _val = min;
        _min = min;
        _max = max;
    }
    public int Value
    {
        get { return _val; }
        set
        {
            if ( value > _max )
                _val = _min;
            else
            {
                if (value < _min )
                    _val = _max;
                else
                    _val = value;
            }    
        }
    }
    public override string ToString()
    {
        return Value.ToString();
    }
    public int ToInteger()
    {
        return Value;
    }
    public static Cycle operator +(Cycle arg1, int arg2)
    {
        arg1.Value += arg2;
        return arg1;
    }
    public static Cycle operator -(Cycle arg1, int arg2)
    {
        arg1.Value -= arg2;
        return arg1;
    }
}

class App {
    static int Main(string[]  args)
    {
        Cycle degrees = new Cycle(0, 359);
        Cycle quarters = new Cycle(1, 4);
        for (int i = 0; i <= 8; i++ )
        {
            degrees += 90; quarters += 1;
            Console.WriteLine("degrees = {0}, quarters = {1}", degrees, quarters);
        }
        return 0;
    }
}

Output:

c:\Windows\Microsoft.NET\Framework\v2.0.50727>CYC.exe
degrees = 90, quarters = 2
degrees = 180, quarters = 3
degrees = 270, quarters = 4
degrees = 0, quarters = 1
degrees = 90, quarters = 2
degrees = 180, quarters = 3
degrees = 270, quarters = 4
degrees = 0, quarters = 1
degrees = 90, quarters = 2

Structs are similar to classes in that you can create groups of fields that can be contained in one unit. As seen above, a struct is nothing more than a light-weight data structure. Shown below is another example of a struct that could help to clarify the difference between reference types and value types:

using System;
{
    /*Structs are typically faster because they sit on the stack
      and are passed ByValue, thus making them */
    /*effective for smaller applications. If you overload the stack, 
      then you would damage your application. */
   
    public struct Customer
    {
        public int CustomerID;
        public string FirstName;
        public string LastName;
        public double SpendingLimit;
    }

    class BusinessRules
    {
        static void Main(string[] args)
        {
            Customer myCust;
            myCust.CustomerID = 2;
            myCust.FirstName = "Bill";
            myCust.LastName = "Miller";
            myCust.SpendingLimit = 300;

            BusinessRules br = new BusinessRules();
            br.AdjustSpendingLimit(myCust); 
            Console.WriteLine("Second="+myCust.SpendingLimit);
        }
        private void AdjustSpendingLimit(Customer cust) 
        {
            // We got a BYVALUE copy, therefore changes we
            // make in this method will not affect the 
            // value of the passed in Customer.
            cust.SpendingLimit = ++cust.SpendingLimit;
            Console.WriteLine("First="+cust.SpendingLimit);
        }
    }
}

So, what did we do here? We created a class called BusinessRules that has its entry point in the Main function. We then created an instance of the Customer struct called myCust so we could populate the values (FirstName, LastName, etc.). Next, we called the AdjustSpendingLimit method and cast in the instance of the structure. The output of this program is 301, 300. The first value is incremented, and will only affect the first value. This is where it is important to understand the difference between a reference type and a value type. When I cast in the structure instance, the structure is automatically passed in by value: only the values of myCust are sent – not the memory locations. When we cast in myCust, that value was printed out to 301 (due to cust.SpendingLimit = ++cust.SpendingLimit) in the console. However, in Console.WriteLine, the next value is 300, because the value was copied and sent to adjust the spending limit, not the actual variable in memory.

What is a Reference Type?

Reference types store the address of their data, also known as a pointer, on the stack. The actual data that the address refers to is stored in an area of memory called the heap. Because reference types represent the address of the data rather than the data itself, assigning a reference variable to another doesn’t copy the data. Instead, assigning a reference variable to another instance creates a second copy of the reference, which refers to the same location of the heap as the original value:

using System;
struct Struct1
{
    public int Value;
}
class Class1 {
    public int Value = 0;
}
class Test
{
    static void Main() {
        Struct1 v1 = new Struct1();
        Struct1 v2 = v1;
        v2.Value = 123;
        Class1 r1 = new Class1();
        Class1 r2 = r1;
        r2.Value = 123;
        Console.WriteLine("Values: {0}, {1}", v1.Value, v2.Value);
        Console.WriteLine("Refs: {0}, {1}", r1.Value, r2.Value);
     }
}

Here is the output:

c:\Windows\Microsoft.NET\Framework\v2.0.50727>ref.exe
Values: 0, 123
Refs: 123, 123

Managing the File System Using the System.IO Namespace

The CLR requires that all objects be created by using the new operator. The following code shows how to make a Student object:

Student st = new Student(“ConstructorParams1”);

When the new operator is called, it performs a series of operations under the hood of the CLR. The newly created reference is saved in the variable st, which is of type Student. Now, when we look at these next examples, notice how the new operator is called, where it returns a reference to a newly created object, and how the syntax works. Shown below are examples that show how to get information about a file: we create a new FileInfo object by using the path to the file. We then access the FileInfo object’s properties:

using System;
using  System.IO;
class App {
    static void Main() {
        FileInfo aFile = new FileInfo(@”C:\windows\system32\config.NT”);
        if (aFile.Exists)
        {
           Console.WriteLine(“Filename   : {0}”,  aFile.Name);
           Console.WriteLine(“Path          : {0}”,  aFile.FullName);
        }
    }
}

Here is the output:

OUTPUT: 
Filename: config.NT
Path    : C:\windows\system32\config.NT

To enumerate files in a directory, you create a valid DirectoryInfo object by using the path to the directory, and then call the GetFiles method to enumerate the files in the directory:

using System;
using System.IO;
class App {
    static void Main() {
        DirectoryInfo ourDir = new DirectoryInfo(@"c:\windows");
        Console.WriteLine("Directory:  {0}", ourDir.FullName);
        foreach (FileInfo file in ourDir.GetFiles())
        {
            Console.WriteLine("File: {0}", file.Name);
        }
    }
}

It is important to remember that object-oriented programming uses the class as the basic unit of organization. The class is an abstraction. For example, a box is an abstraction, but it may contain 10 oranges. The ten oranges are the ten concretely defined object members. All types (or classes) are ultimately derived from System.Object.

Consider a program that generates 10 random bytes and encodes them in base64:

using System;

public static class Program {
   public static void Main() {
      // Get a set of 10 randomly generated bytes
      Byte[] bytes = new Byte[10];
      new Random().NextBytes(bytes);

      // Display the bytes
      Console.WriteLine(BitConverter.ToString(bytes));

      // Decode the bytes into a base-64 string and show the string
      String s = Convert.ToBase64String(bytes);
      Console.WriteLine(s);

      // Encode the base-64 string back to bytes and show the bytes
      bytes = Convert.FromBase64String(s);
      Console.WriteLine(BitConverter.ToString(bytes));
   }
}

There is a tool called the .NET Reflector. This tool shows in a hierarchal fashion what classes derive from the previous ones that form the hierarchal structure of the C# namespace order. From there, you can find what methods the classes support, and what type of data those manipulate. Just follow through the program flow of the examples above, and continue to write code and understand why one line leads to another to form a program that achieves an end result.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
GeneralProperty set. PinmemberPaulo Zemek4-Nov-09 10:41 
GeneralRe: Property set. Pinmentorlogicchild5-Nov-09 10:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 24 Dec 2008
Article Copyright 2008 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid