Types by VALUE and REFERENCE in C#

In this post, we will explore the difference between value types and reference types, and we'll also show how C# works with both data types and the options we have.

 

 

1 - Difference between value types and reference types

To determine when we should create or use a struct, a class, or even a record, we must know their characteristics, limitations, and how they work in terms of memory.

 

1.1 - Value types

When we use value types in C#, we are referring to structs

 

But what does value type mean?

It means that when we use that object, we are using the object itself, reading and writing to the stack (we'll see what the stack is below), and when we write an object to the stack, we do so in full, not as a pointer like with reference types.

tipos por valor

The most common case of a struct is when it represents a single value, such as a primitive type (double, int, decimal, etc). Also, it should be less than 16kb.

 

When we use the assignment operation (=) with a struct, what we are doing is copying the value into a new variable, not assigning the reference. So, if we modify one of the objects, the value of the other doesn't change:

int ejemplo1 = 15; // asignamos un valor inicial
int ejemplo2 = ejemplo1; // asignamos ejempo2 con el valor de ejemplo1
ejemplo2 = 10; // modificamos el valor de ejemplo 2
Console.WriteLine(ejemplo1); // imprime 15;
Console.WriteLine(ejemplo2) // imprime 10;

This is important because reference types do not work in this way. 

 

 

1.2 - Reference types

When we use reference types in C#, we are talking about classes, i.e., class, and since C# 9, also records. All instances of classes are located in the heap.

Ultimately, the variable itself is a pointer to that object in the heap, not the actual object.

tipos por referencia

What does it mean that classes are pointers to memory? 

Imagine we have a class called Vehicle that contains properties such as make, model, and number of doors. 

When we do an assignment like the following: 

Vehiculo vehiculo1 = new Vehiculo(“Opel”, “Astra”, 4);

We are creating vehiculo1 in the heap and assigning its memory position to its value;

So if we create a vehiculo2 and assign it the value of vehiculo1, what we are actually doing is assigning the variable the pointer to the same memory position.

Vehiculo vehiculo1 = new Vehiculo("Opel", "Astra", 4); //Creamos un vehículo
Vehiculo vehiculo2 = vehiculo1; //asignamos el vehiculo2 con el valor de vehiculo1
vehiculo2.Model = "Vectra";
Console.WriteLine(vehiculo1.Model); // imprime vectra;
Console.WriteLine(vehiculo2.Model) // imprime vectra;

And as we see in the example, when we change the value of one object, "both" are updated.

 

All this pointer usage, unlike in other languages like C, C# does automatically behind the scenes

 

The main reason why we have to do a lot of checks in C# to see if our objects are null is because of this, as the variable is actually a pointer and not the actual value.

Note: the pointer adds another 8 bytes of memory to the object's weight (in a 64-bit program), and another 16 bytes per object are added for C#'s internal use, such as the garbage collector.

 

Since C# 9, we have the option of using records. We mainly use them to create immutable reference types in C#

 

 

2 - Difference between Heap and Stack

When we create an object in code, it takes up space in memory, and for that, we have two options: the heap and the stack. Here we will see how they differ and how they work. 

 

2.1 - What is the stack?

The stack is a contiguous memory area, which is allocated from the lowest to the highest memory position, in order. And when we want to free it, we do so from the highest to the lowest position. 

 

This means that to free a position in the middle, we must free everything placed at a higher position.

stack representation

 

To know if a memory point is allocated, we use a pointer that points to a position in memory, and when we deallocate memory, all we do is move the pointer one place down; we do not clean the memory space. The next time we assign a value, it will just overwrite the upper value. 

stack workings

 

As we see in the image, when deallocating the value, what we do is move the pointer to the position with "valor 1", but the previous position still keeps its value. 

 

Once we assign a new value, in this case "valor A", we overwrite it at the previous position. 

 

One of the great benefits of using the stack for allocations is that it is very efficient and works great for example in local functions. When you define a function in your code, all variables go to the stack and when you exit the function, they are wiped out.

 

As a final note, in C# we have a 16-byte limit for structs (they go to the stack). This is because, since they're passed by value, the whole element must be copied, and with 16 bytes, it can be done with just a couple of processor instructions. If it were larger than that, you'd lose the stack's performance advantage, due to the cost of copying the entire element. 

 

 

2.2 - What is the heap?

As we have seen, the stack has some restrictions, so we can't always use it. This is where the heap comes into play. 

 

Heap is the memory we use for dynamic memory allocation

And as its name suggests, we allocate (and deallocate) memory in a disorganized way, which may cause fragmentation.

uso del stack

What do I mean by fragmentation?

As we can see, we have several blocks of allocated memory (blue, orange, green, purple colors) and several free blocks (white). If we wanted to store an object that takes up 3 blocks, we couldn't, as we don't have 3 consecutive free blocks. 

 

Usually, we use the heap for all those objects whose lifespan goes beyond a function or a process.

 

Finally, we mentioned that the stack is cleaned up automatically when exiting the function. In the heap this is not the case , the developer has to clean up the heap. Luckily, C# knows, thanks to the extra 16 bytes stored per object, when an object in memory will no longer be used, and then the garbage collector goes through that memory and cleans up what's still allocated but not going to be used again. 

 

 

3 - When to use struct, class or record

Now comes the big question: with all this information, when we're writing code, which data type should we use?

 

3.1 - When to use struct

We can create our type as a struct if it meets the following characteristics (all of them): 

  1. The instance is small (16 bytes) and its lifetime is usually short, for example we only use it within a function.
    • It's also common if it's going to be part of another object and never an independent "root" object. 
  2. One of the main characteristics of value types: if it is going to be immutable.
  3. Finally, if it will not be constantly converted to a reference type (boxed).

If our type meets all these characteristics, then it should be a struct

Otherwise, it should be a reference type.

 

3.2 - When to use record

Our type is going to be immutable.

Note: for me, the perfect example of a record is a DTO of our API. Since we will never want to change its content and at the same time it saves us from having to write a lot of code.

 

3.3 - When to use class

If our object does not comply with the previous properties (of structs or records), the type we should use is class.

 

There are a few more things to consider, for example records do not implement the IComparable interface or that structs do not support inheritance. But in 99% of cases, the guidelines above apply.

 

 

Conclusion

  • In this post, we've seen what value types and reference types are, and their differences. 
  • We've seen the difference between memory located in the stack and memory located in the heap.
  • Finally, we've seen when to use struct, when to use record, and when to use class in C#.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café