Reference types and value types in .NET

Tags: ,

In .NET (and therefore C#) there are two main sorts of type: reference types and value types! Understanding these sorts of type is crucial in the .Net ecosystem and, more generally, in object oriented programming. There are some clear definitions of these concepts that anybody could learn fairly easy, but really understanding how reference types and value types work is sometimes a little bit harder. And I must confess that it took me some time to achieve a certain level of familiarity. Furthermore, if you really want to understand how reference types and value types work, you need to get your hands dirty an play around with them. In this article I will try to explain reference types and value types as good as I can, starting from some dry (but important) definitions that I will try to make more vivid using code samples.

Note: I also made a YouTube video on this topic with some more graphical representations of these concepts. You might want to check it out:

Reference types

A reference type is a type which has as its value a reference to the appropriate data rather than the data itself. Classes , interfaces, delegates and arrays are all reference types. Let’s look into some code!


Random rnd = new Random();

Well, that’s simple! Even though I know that all readers will understand what happens here, I just want to think out loud for a while. So what did we do? Well, we declared a variable “rnd”, created a new Random object and assigned to “rnd” a reference to the object as value.  So, the value of “rnd” is not the object itself, but a reference to the object. In other words we could even say that the value of “rnd” is a  memory location where the Random object resides. This means that we could have another variable “rnd2” that holds a reference to the same object.


Random rnd = new Random();
Random rnd2 = rnd;

Here we created a second variable rnd2 and assigned the value of rnd to it. And since the value of rnd is a reference to the Random object, what we assigned to rnd2 is exactly the same reference. In other words, we could say the the value of rnd2 is a memory location where the initial Random object resides. And this could allow us to do some fancy stuff. But let’s use a StringBuilder object instead, since it’s easier to demonstrate.


//variable initial has as value a reference to the StringBuilder object
StringBuilder initial = new StringBuilder();
initial.Append("Hello");

//variable second gets the value of initial, which is a reference
//to the same object
StringBuilder second = initial;

//We append something to second and print initial to the the console.
//Gues what gets printed?
second.Append(" world!");
Console.WriteLine(initial);
Console.WriteLine(second);

Since both initial  and second have as value a reference to the same object, when we perform an append operation it will change the properties of the object itself. So that’s why no matter if we print initial or second to the console, we will see the entire “Hello world!” output and not only “hello” or only “world”.

Value types

While reference types have a layer of indirection between the variable and the real data, value types don’t. Variables of a value type directly contain the data. Assignment of a value type involves the actual data being copied. Simple types (such as float, int, char), enums and structs are all value types. Let’s look into some code using a custom struct.


struct IntContainerStruct
    {
        public int i;
    }
IntContainerStruct first = new IntContainerStruct();
first.i = 10;
IntContainerStruct second = first;
second.i = 15;

//Guess the output?
Console.WriteLine(first.i);
Console.WriteLine(second.i);

//

These are the exact same steps that we performed on the StringBuilder class (which is a reference type). However, when we use a struct (which is a value type) we can see that the output is 10 and  15. So, in value types the values are totally independent. The value in second is independent of the value in first apart from when the assignment takes place.

Reference types and value types head to head

To better illustrate a head to head behavior between reference types and value types, let’s create also an IntContainerClass. We will be able to take the class and the struct through the same steps and observe how they behave.


class IntContainerClass
{
public int i { get; set; }
}

IntContainerStruct first = new IntContainerStruct();
first.i = 10;
IntContainerStruct second = first;
second.i = 15;

IntContainerClass firstC = new IntContainerClass();
firstC.i = 10;
IntContainerClass secondC = firstC;
secondC.i = 15;
Console.WriteLine(first.i);
Console.WriteLine(second.i);
Console.WriteLine(firstC.i);
Console.WriteLine(second.i);

//Output

//10

//15

//15

//15

That’s cool, isn’t it? So we first did our magic on the struct, and when we printed out the two different struct instances, each held its own value. Then we did the same magic on the class and since the class is a reference type, when we set secondC to 15 it changed the value on the object itself. And since the same object was referenced by two different instances, no matter which instance we print to the console, it will display the same value.

Heap and stack

You will often hear a rule of thumb saying that “value types go on the stack, while reference types go on the heap!” While this statement might be true in a lot of circumstances it is certainly not always true. In fact, there was an interesting discussion on this topic between .Net gurus on GitHub few weeks ago and all of them agreed that value types might even go on the heap, while reference types might also go on the stack. Jon Skeet (yeah, he’s one of the gurus) also has an extensive article about this exact topic on his website.  Here’s a brief summary of some key points:

Each local variable (ie one declared in a method) is stored on the stack. That includes reference type variables – the variable itself is on the stack, but remember that the value of a reference type variable is only a reference (or null), not the object itself.

Instance variables for a reference type are always on the heap.

Instance variables for a value type are stored in the same context as the variable that declares the value type. The memory slot for the instance effectively contains the slots for each field within the instance. That means (given the previous two points) that a struct variable declared within a method will always be on the stack, whereas a struct variable which is an instance field of a class will be on the heap.

Every static variable is stored on the heap, regardless of whether it’s declared within a reference type or a value type.

Please check Jon Skeet’s page for more details. I’m surely not able to explain everything as clear as he does!

I’m also not in a position counter Jon Skeet’s arguments, but in my opinion if you’re a .Net developer with under 3-5 years experience, it’s not the end of the world to say that value types go on the stack and reference types go on the heap. In fact, for beginners I think it might be even useful to memorize this “mantra” because it could prove helpful to better understand the surface of the entire .Net ecosystem. It will then be easier after some time to grasp that the “mantra” might not always be true. Investing too much brain and energy in these syntactic sugar details as a beginner might even slow down your progress.

What about strings?

This article already got very long so I won’t dwell on this topic for too long. In fact I am already working on another article dedicated to strings. However, till I finish it, we have to note that “string” is a class in C# and therefore is a reference type. There is however a lot of confusion about strings because in a certain way they behave similar to value types. Still, they remain an immutable reference type.

Please don’t be shy and share this article if you found it useful. Also, don’t be shy and hit me with your comments if you have anything to add, give any type of feedback or anything else. I’m glad when I can get in touch with my readers. Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *