Click to share! ⬇️

C Sharp Reference Types

This tutorial will cover the different types available in the C# Programming language. In C#, every type belongs to one of two categories. These are Reference Types and Value Types. Reference Types and Value Types behave quite differently in a program so it’s important to understand how these different types work. The best way to do this is to simply write some sample code, maybe create some unit tests, and inspect the results in Visual Studio.


C# Reference Types

When a programmer defines a class in C#, that class becomes a Reference Type. In the classes and objects tutorial we had created a StockPortfolio class. Therefore, we have already created a reference type! When a variable is typed, meaning there is a type definition before the variable name, it holds a reference – also known as a pointer – to an object in memory. It holds an address pointing to the object in memory. There can be several variables that all point to the same object. The best way to understand this is to simply test a few things out in Visual Studio which we’ll do shortly.

  • Variables store a reference to an object
  • Multiple variables can point to the same object
  • A single variable can point to multiple objects over it’s lifetime
  • Objects are allocated into memory using new

reference type diagram

To test some of these concepts out, let’s revisit the StockPortfolio class and add a Name to our properties like so.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks
{
    public class StockPortfolio
    {
        public StockPortfolio()
        {
            stocks = new List<float>();
        }

        public StockStatistics ComputeStatistics()
        {
            StockStatistics stats = new StockStatistics();


            float sum = 0;
            foreach (float stock in stocks)
            {
                stats.HighestStock = Math.Max(stock, stats.HighestStock);
                stats.LowestStock = Math.Min(stock, stats.LowestStock);
                sum += stock;
            }

            stats.AverageStock = sum / stocks.Count;
            return stats;
        }

        public void AddStock(float stock)
        {
            stocks.Add(stock);
        }

        public string Name;

        private List<float> stocks;
    }
}</float></float>

Now, we can go back to the Program.cs file and create a new StockPortfolio object and place it in the portfolio1 variable. Then, we will assign portfolio1 to portfolio2. We can assign a name to portfolio1, and then write out the name of portfolio2 to the console. What do you think will happen?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI.DataVisualization.Charting;

namespace Stocks
{
    class Program
    {
        static void Main(string[] args)
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Console.WriteLine(portfolio2.Name);
        }
    }
}

When we run the program we see this result.
console writeline output

How is this happening? We assigned a name to the first variable, yet we wrote out the value in the second variable and it appears to have the same value that is in variable 1! This is because both variables point to the same object in memory.
two variables point to same object


Testing Types With Unit Tests

Now that we know how to set up unit tests in visual studio, we can set up some tests against types in C# to see if we get the results we expect. Not only are unit tests good for testing your application code, but they are also great for when learning a new topic in the language, like a new API or library you might want to use. With unit tests, you can devise some experiments, and then run the tests to see if you get what you expect. Let’s add a new class to our Stocks.Test project.
add class to vs project

Go ahead and give the file a name of ReferenceTypeTests.cs like so.
naming new class in visual studio

Once the file is in place we can create a new TestMethod which is a public void returning method. The name of this method is VariablesHoldAReference. It’s a good practice to name your test methods by what they are going to test or prove for you. The goal of this test is to instantiate a new StockPortfolio object and have two variables point to the same StockPortfolio object. We want to assert that these two variables are pointing to the same object. It’s basically the same thing we did above but instead of writing the result out to the console, we are using an assertion from the test framework to give us a true or false result.
In this new file, we need to add this code.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks.Test
{
    [TestClass]
    public class ReferenceTypeTests
    {
        [TestMethod]
        public void VariablesHoldAReference()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Assert.AreEqual(portfolio1.Name, portfolio2.Name);
        }
    }
}

Now, let’s run the test and observe that yes it is passing!
passing unit test in visual studio

So it looks like we are getting the hang of reference types. A great way to keep learning is to try different experiments with different variables and write some assertions based on what you believe should happen, and then run the test to see if your beliefs are correct.


C# Value Types

In C# there are two categories of types as mentioned above. Those are the Reference Type, and now the Value Type. A value type in C# does not hold a pointer, rather it holds the actual value. Integers and Floats are two examples of value types in C#. Consider a variable of foo which is an integer type. When the foo variable is used in a method and assigned a value of say 50, that value is stored right in the memory address of the foo variable. It is not a reference or pointer. Now, let’s say we have another integer named bar. We assign it the value of 25. We now have two different variables that directly hold two different values. This is very different from when we created a StockPortfolio reference type and had two variables pointing to the same object.


Why Value Types?

Value types are important because they are efficient and faster to allocate in memory than reference types. Creating an object takes more processing power and memory than creating a value type like an integer, and an application might need millions of value types to exist. Using strictly reference types will get too expensive. So value types are used because they use minimal memory and are very fast. Storing an entire object, or reference is much more intensive. Primitives are typically value types that store themselves directly in a variable. These value types are generally immutable, meaning you cannot change the value of a value type. Do not confuse this with assigning a different value to an integer variable though. For example we can assign the variable bar a different value. We could change it from 25 to 10, but that value 25 and that value 10, are values that do not change. The value 25 is always the value 25 and the value 10 is always the value 10. We can create a unit test to show how value types work, we’ll call it IntVariablesHoldAValue().

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks.Test
{
    [TestClass]
    public class ReferenceTypeTests
    {
        [TestMethod]
        public void IntVariablesHoldAValue()
        {
            int foo = 50;
            int bar = foo;

            foo = 25;
            Assert.AreNotEqual(foo, bar);
        }

        [TestMethod]
        public void VariablesHoldAReference()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Assert.AreEqual(portfolio1.Name, portfolio2.Name);
        }
    }
}

To really see how this works, we can use the debugger with a break-point and step through the execution of the program while watching the values that are stored in the variables step by step. First, we set a breakpoint. To do this, you can just click in the left-hand gray area and you’ll see a red dot show up. This indicates that when the program starts to run, it will halt at this point so you can inspect all the values and states in the program.
how to set break point in visual studio


Debug Tests

Now, you can start debugging this unit test by clicking on Test->Debug->All Tests. This differs from debugging standard code, it works a little differently when debugging test code. The debugger launches and stops right at the break point. Notice the little yellow arrow pointing to the right.
program stopped at breakpoint


Step Into

At this point, the program is frozen in time so to speak. Now, we can move through the program one step at a time by using the Step Into feature. Go ahead and press the F11 key to move forward. The little yellow arrow now moves down one line and in the Autos pane, we see the foo variable is now in existence but has not yet been assigned a value.
first step into


Second Step Into

On the second Step Into, the yellow arrow moves down again and now we can see that foo does have a value of 50 – it has now been assigned. In addition, bar is now in existence, but has not yet been assigned a value.
second step into


Third Step Into

The third step into brings the yellow arrow down another line in the program. So you can see how when using the debugger and the step into feature it allows you to see exactly how the states of the variables update one step at a time. At this point in the program both the foo and bar variables hold the value of 50. Notice that we are stopped on the line where it says foo = 25. In the autos pane foo is still 50. This is because this line has not been executed yet, it will on the next step.
3rd step into


Fourth Step Into

The fourth step into gets us to a point where bar has the value of 50, but notice the value of foo. It now has a value of 25. So you can see that we can overwrite an existing value in a variable that is a value type.
4th step into


Fifth Step Into

On the last iteration, the assertion has been run, and it is true that 25 does not equal 50.
5th step into

So for value types, we know the following.

  • Variables hold the value
  • No pointers, no references
  • Many built-in primitives are value types
  • int, double, float

The struct keyword

In the section above, it should be clear what a value type in C# is. Now we can ask, how do we create our own value type in a program. The answer is by using a struct. A struct definition looks just like a class definition. They both have a name, an opening and closing curly brace, and some code inside of them. A struct can also make use of access modifiers like public and internal. In most cases, you would want to use a class and not a struct. However, there are use cases for using the struct, and it is typically for when you need to write an abstraction that represents a single value. It’s a more simplified scenario than using a class. Structs work best when they contain a small amount of data since value types get copied around in memory often. The key to understand more than anything else however is that Structs are value types, unlike classes, which are reference types. Here is an example of a struct.

public struct StockTicker
{
    public decimal price;
    public string company;
    public string symbol;
}

The enum keyword

In addition to using a struct to create a value type in C#, you can also use an enum. An enumeration is good for working with named constants in an application. For example, say we have this stock application and we need to determine if a stock is traded on the Nasdaq, NYSE, or maybe the OTC market. You could categorize each exchange and assign it a number. So Nasdaq would be 1, NYSE 2, and OTC would be 3. The issue is that in the source code, those numbers turn into magic numbers. You start referencing numbers in the code and forget what they actually represent. So instead, you could create an enum. Consider this example.

public enum TickerExchange
{
    Nasdaq = 1,
    NYSE,
    OTC
}

Now with that enum in place, you can write code like you see in this if statement. It becomes more clear and readable when using an enum instead of magic numbers.

if(ticker.Exchange == TickerExchange.Nasdaq)
{
    Console.WriteLine("The stock trades on the Nasdaq");
}

You might be wondering why in the enum definition only the Nasdaq appears to be assigned a value. That is because the enum automatically assigns the value for each successive enum member. So NYSE equals 2, and OTC equals 3, yet all we had to manually assign is the first one.

  • An enum creates a value type
  • A set of named constants
  • Underlying data type is int by default

To see this in action we can do a string comparison using an enum.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks.Test
{
    [TestClass]
    public class ReferenceTypeTests
    {
        [TestMethod]
        public void IntVariablesHoldAValue()
        {
            int foo = 50;
            int bar = foo;

            foo = 25;
            Assert.AreNotEqual(foo, bar);
        }

        [TestMethod]
        public void VariablesHoldAReference()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Assert.AreEqual(portfolio1.Name, portfolio2.Name);
        }

        [TestMethod]
        public void StringComparisons()
        {
            string ticker1 = "AAPL";
            string ticker2 = "aapl";

            bool result = String.Equals(ticker1, ticker2, StringComparison.InvariantCultureIgnoreCase);
            Assert.IsTrue(result);
        }
    }
}

In the highlighted code above ticker1 points to the string “AAPL”. The ticker2 variable points to “aapl”, the same ticker but lowercase. From there we can do a StringComparison but ignore the case. Now, StringComparison is an enum, and the IntelliSense will show you as much.
StringComparison enum

Notice we see the various StringComparisions that are available. We can see CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, and OrdinalIgnoreCase. Now check this out. We can right-click on StringComparison and then select “Go To Definition”.
visual studio go to definition

This brings up the metadata view and shows us exactly how this enum is defined. We see each option has a value associated with it. The C# complier expects one of these StringComparison values. This is a benefit of using strong typing and using enums instead of magic numbers.
metadata stringcomparison enum

This code:

String.Equals(ticker1, ticker2, StringComparison.InvariantCultureIgnoreCase);

Is more meaningful to the programmer than this code:

String.Equals(ticker1, ticker2, 3);

And just for grins, we’ll run all the tests. This new test is passing. So we know that “AAPL” and “aapl” are equal when doing a case insensitive comparison and ignoring culture.
stingcomparison test passing


Parameters Are Passed By Value In C#

When you call a method that takes a parameter, the value in the variable you pass will be copied into the variable that is a parameter to the method. What you pass is always a copy and that means for reference types you are passing a copy of the reference inside the variable. If a value type is passed to the method, it is a copy of the value inside the variable.


Passing Reference Type By Value

If there is a method in our code that takes a parameter of type StockPortfolio for example, that method gets a copy of the pointer(or reference) to a StockPortfolio object. This means the calling code and the method being called both have pointers to the same object. Let’s see an example of a method getting a copy of the pointer in Visual Studio.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks.Test
{
    [TestClass]
    public class ReferenceTypeTests
    {
        [TestMethod]
        public void IntVariablesHoldAValue()
        {
            int foo = 50;
            int bar = foo;

            foo = 25;
            Assert.AreNotEqual(foo, bar);
        }

        [TestMethod]
        public void VariablesHoldAReference()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Assert.AreEqual(portfolio1.Name, portfolio2.Name);
        }

        [TestMethod]
        public void StringComparisons()
        {
            string ticker1 = "AAPL";
            string ticker2 = "aapl";

            bool result = String.Equals(ticker1, ticker2, StringComparison.InvariantCultureIgnoreCase);
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void ReferenceTypesPassByValue()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            GivePortfolioName(portfolio2);
            Assert.AreEqual("Paul Tudor Jones Portfolio", portfolio1.Name);
        }

        private void GivePortfolioName(StockPortfolio portfolio)
        {
            portfolio.Name = "Paul Tudor Jones Portfolio";
        }
    }
}

In the test code highlighted above the first thing we do is to create a new object and place it in the portfolio1 variable. Then we assign the reference in portfolio1 to portfolio2. We now have two variables pointing to the same object. After that the GivePortfolioName method is called. This method is not part of the test, but it does some work for us. It takes a StockPortfolio parameter and sets the name of that StockPortfolio. The method sets it to “Paul Tudor Jones Portfolio”. In the ReferenceTypesPassByValue() method, GivePortfolioName() is called passing in portfolio2. When GivePortfolioName() is is called, the value inside of portfolio2 is copied into the portfolio parameter and that value is a pointer. This means that during the running of the test, three variables that are pointing to the same StockPortfolio object: portfolio1, portfolio2, and portfolio. Any changes made to the StockPortfolio through any of those variables will be visible if we look at that object through any of the other variables. In other words portfolio.Name, portfolio1.Name, and portfolio2.Name will all be “Paul Tudor Jones Portfolio”.

We can see in the debugger that just before the GivePortfolioName() method is called, we have two variables both pointing to one StockPortfolio object.
two variables one object in debugger

However! After that method runs, look at the Name property of both portfolio1 and portfolio2. They both show the string “Paul Tudor Jones Portfolio” because they are all pointing to the same object.
setting object property using a method


Passing Value Type By Value

Now, we’ll look at a test where a parameter is passed by value when the parameter is of type value. Yes a bit confusing, but let’s see how this works.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stocks.Test
{
    [TestClass]
    public class ReferenceTypeTests
    {
        [TestMethod]
        public void IntVariablesHoldAValue()
        {
            int foo = 50;
            int bar = foo;

            foo = 25;
            Assert.AreNotEqual(foo, bar);
        }

        [TestMethod]
        public void VariablesHoldAReference()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            portfolio1.Name = "Warren Buffet's Portfolio";
            Assert.AreEqual(portfolio1.Name, portfolio2.Name);
        }

        [TestMethod]
        public void StringComparisons()
        {
            string ticker1 = "AAPL";
            string ticker2 = "aapl";

            bool result = String.Equals(ticker1, ticker2, StringComparison.InvariantCultureIgnoreCase);
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void ReferenceTypesPassByValue()
        {
            StockPortfolio portfolio1 = new StockPortfolio();
            StockPortfolio portfolio2 = portfolio1;

            GivePortfolioName(portfolio2);
            Assert.AreEqual("Paul Tudor Jones Portfolio", portfolio1.Name);
        }

        private void GivePortfolioName(StockPortfolio portfolio)
        {
            portfolio.Name = "Paul Tudor Jones Portfolio";
        }

        [TestMethod]
        public void ValueTypesPassByValue()
        {
            int num = 25;
            IncrementNumber(num);
            Assert.AreNotEqual(26, num);
        }

        private void IncrementNumber(int number)
        {
            number += 1;
        }
    }
}

When this test runs, the integer of num is set to 25 in the ValueTypesPassByValue() method. Right after that we call the IncrementNumber() passing in a copy of the value, *not* a copy of a pointer! This changes the behavior significantly. Out in the IncrementNumber() method, 25 now becomes 26, but it is a copy of the value so the original num existing in ValueTypesPassByValue() stays at 25 now matter what happens in the IncrementNumber() method! Therefore, when we assert that 26 is not equal to num, this is true. Hopefully, these two examples show that even though all parameters are passed by value in C#, the results can be different based on whether it is a Reference Type or a Value Type being passed!

  • Parameters are passed “by value”
  • Reference types pass a copy of the reference
  • Value types pass a copy of the value

Value Types Are Immutable

It is important to note that Value Types are immutable. That’s just a fancy way of saying you can not change them. This just means that once you create a value, you cannot change the value. Don’t be confused however as that doesn’t mean that the value stored in a variable cannot change. Variables are called that because the data inside can vary, but the actual value of the value type cannot change. For example, you cannot change the value of the integer value 25. The value 25 is always the value 25. An interesting aspect of immutable value types is the DateTime value type. Consider the following new test in our test class.

[TestMethod]
public void AddDaysToDateTime()
{
    DateTime date = new DateTime(2019, 1, 1);
    date = date.AddDays(1);

    Assert.AreEqual(2, date.Day);
}

The code above is creating a new date time and storing it in the date variable. Here are the contents of that variable immediately after being assigned the DateTime.
C Sharp DateTime

The entire contents of this DateTime are immutable, once created they cannot be changed. Notice that the Day property has a value of 1 to represent the first day. When we then call date.AddDays(1), it would seem that Day should be incremented to 2. Then we do an assert to verify if the number 2 is equal to date.Day(). Guess what happens. The test fails.
c sharp datetime is immutable

Why does this happen? The reason for this is that AddDays does not change the underlying DateTime value. What AddDays actually does is return a new DateTime instance. That means that returned instance is the updated one, not the original. So if we simply modify the code by capturing that returned instance into a variable, the code will pass.

[TestMethod]
public void AddDaysToDateTime()
{
    DateTime date = new DateTime(2019, 1, 1);
    date = date.AddDays(1);

    Assert.AreEqual(2, date.Day);
}

The same type of thing happens with Strings in C#. String variables are a reference that point to a sequence of characters. When passing strings to different methods, passing a reference is good because copying the entire value of the string can be expensive if it is a really large string. This means a String is actually a Reference Type, yet behaves like a value type since it is immutable. Check out this test code.

[TestMethod]
public void UppercaseString()
{
    string ticker = "nflx";
    ticker.ToUpper();

    Assert.AreEqual("NFLX", ticker);
}

Looks good right? We have a lowercase “nflx” and then call the ToUpper() method on it. Then we assert that “NFLX” should equal the result of us just calling that ToUpper() method on “nflx”. Once again, this test fails. This is because even though string is a reference type, it behaves very like a value type. We expected an all uppercase NFLX, but we got an all lowercase nflx. The reason for this is because String methods like Trim, ToUpper, and ToLower, don’t modify the string that I’m pointing to. They do not modify the underlying value itself. Instead, they create a new string and return that string from the method. Therefore, we need to capture that by assigning a reference to the new modified string back into the ticker variable like so.

[TestMethod]
public void UppercaseString()
{
    string ticker = "nflx";
    ticker = ticker.ToUpper();

    Assert.AreEqual("NFLX", ticker);
}

The Array Reference Type

The Array in C# is a data structure to store a collection of multiple objects or values. An array itself is always a Reference Type. There is also a similar type of data structure called a List which is slightly different. Arrays have a fixed size which you must specify prior to using it. A List on the other hand just automatically grows as you add new items to it. So if you know exactly how many values you will need to work with, go with an Array. If you need that dynamic sizing since you don’t know how many values you might work with, go with a List. Both the List and the Array are 0 indexed, meaning the first item in a list or an array starts at index 0 just like most all programming languages.

We can see how Arrays are a Reference Type using this test code.

[TestMethod]
public void UsingArrays()
{
    float[] stocks;
    stocks = new float[8];

    AddStocks(stocks);

    Assert.AreEqual(27.22f, stocks[5]);
}

private void AddStocks(float[] stocks)
{
    stocks[5] = 27.22f;
}

We set up an array that holds floats and it has a size of 8. Then, we see that we make have a method called AddStocks which takes a reference to an array of floating-point numbers as a parameter. Inside the method, we say stocks sub 5 = 27.22f. You might be wondering since float is a value type, will the calling method using arrays actually see this value placed into the array? Yes it will because an array is a reference type, and even though what’s inside is a value type, that number is stored inside the array, and both the stocks variable inside of using arrays and the stocks variable that is the parameter to AddStocks, those are both references to the exact same array.


Reference Types And Value Types In C# Summary

  • Every type is a value type or reference type
  • Use struct to create a value type
  • Use class to create a reference type
  • Arrays and strings are reference types
  • Strings behave like a value type

In this tutorial we saw that every type in C# falls into one of two categories. Every type is either a Reference Type or a Value Type. You can create a new value type with the struct keyword. In most cases, it makes sense to use Reference Types and the class keyword. We also saw some interesting concepts regarding strings and arrays, which are both reference types. A String behaves like a value type because strings are immutable. You can only build new strings, you can’t modify existing strings.

Click to share! ⬇️