Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Value vs. Reference Types in C#

Recommended resources

Hello! In our last lesson, we focused on the high-level structure of a C# program, drawing parallels between C#'s modern syntax and the Java conventions you're familiar with. We saw how features like top-level statements and implicit usings help reduce boilerplate code.

Today, we'll move from the structure of the code to the data it manipulates. This lesson tackles a fundamental concept in C# that has a direct parallel in Java: the distinction between different kinds of types. You already know about Java's primitive types (int, double, etc.) and its reference types (objects). C# has a similar, but more explicit and powerful, system of value types and reference types. Understanding how they work, and especially how they behave in memory, is one of the most critical steps in mastering C# and writing efficient, bug-free code.

By the end of this lesson, you will be able to use C#'s value and reference types and explain their distinct memory behaviors.

1. The Two Kinds of Memory: Stack and Heap

Before we can discuss value and reference types, we need to understand the two main places in memory where your application stores data during execution: the Stack and the Heap. Given your background in computer science and DSA, you are likely familiar with these data structures, but let's quickly review how they apply in the context of a running .NET application.

  • The Stack is a region of memory that works in a Last-In, First-Out (LIFO) manner. It's highly organized and extremely fast. The stack is used to store data that has a well-defined, short lifetime, such as a method's local variables and parameters. When a method is called, a "stack frame" containing its local variables is pushed onto the stack. When the method finishes, its frame is popped off, and the memory is instantly available again. This cleanup is automatic and very efficient.

  • The Heap is a much larger, less organized region of memory used for dynamic allocation. This is where objects (i.e., instances of classes) that can have a longer or more complex lifetime are stored. Unlike the stack, memory on the heap is not automatically deallocated when a method finishes. Instead, the .NET runtime uses a Garbage Collector (GC) that periodically runs to find objects on the heap that are no longer referenced by any part of the application and frees that memory.

The article "Stack and Heap Memory in .NET" provides a great walkthrough of how these memory areas are used when a method executes.

Stack and Heap Memory in C# with Examples - Dot Net Tutorials

This article explains the fundamental concepts of stack and heap memory within the .NET framework. It uses a step-by-step code example to show how memory is allocated and deallocated as a method executes.

Please read from the beginning of the article until the heading "Why do we have two types of memory?". Pay close attention to how int variables are placed on the stack, while a class object involves both the stack (for the reference) and the heap (for the object itself).


2. Value Types and Reference Types

Now, let's connect this back to C# types. Every type in C# falls into one of two categories, and this category dictates its memory behavior.

  • Value Types directly contain their data. They are typically stored on the stack when used as local variables. Examples include int, double, bool, char, and, most importantly, any struct.

  • Reference Types do not store their data directly. Instead, they store a reference (or a memory address) that points to where the actual data is located on the heap. Examples include class, string, array, and interface.

The following diagram illustrates this perfectly. For the code shown, a and b are int (a value type), so their values (10 and 20) are stored directly on the stack. In contrast, obj is an instance of a class (a reference type). The actual object data lives on the heap, and the variable obj on the stack just holds a reference pointing to it.

A visual representation of how value types (`int`) and reference types (`class`) are allocated in Stack and Heap memory during a method's execution.

The most significant consequence of this distinction appears when you assign one variable to another.

  • When you assign a value type variable, the data is copied. The new variable gets its own independent copy.
  • When you assign a reference type variable, the reference is copied. Both variables now point to the same object on the heap.

This is a critical concept, and it's where your Java experience provides a solid foundation. This is identical to how Java's primitives (int, boolean) are copied by value, while object assignments copy the reference. The key difference in C# is that you can define your own value types using the struct keyword.

The article from TutorialsPoint provides clear code examples that demonstrate this exact behavior.

Explain and contrast value types and reference types in C#

This resource offers a concise comparison of value and reference types with simple, effective code examples and a summary table.

Read the sections Value Types and Reference Types. Pay close attention to the Point struct and User class examples. See how changing p2 does not affect p1, but changing u2 does affect u1. Finally, review the summary table at the end.


3. Choosing Between struct and class

In Java, every custom type you create is a class, which is a reference type. C# gives you a choice: struct (value type) or class (reference type). This is a powerful design decision.

The infographic below provides a fantastic "at a glance" summary of the differences and when to use each.

An infographic comparing C# Value Types and Reference Types, including their memory storage, copy behavior, and key considerations for choosing between a `struct` and a `class`.

As a general rule of thumb:

  • Use class (Reference Type) by default. Most of the objects you design will represent entities that have a distinct identity and potentially complex behavior. Classes support inheritance and are more flexible for larger, long-lived objects.
  • Use struct (Value Type) for small, data-centric types. Think of types that are conceptually like a single value, such as a 2D point (X, Y), a color (R, G, B), or a complex number. Structs are ideal when the type is small (generally under 16 bytes) and immutable (its values don't change after creation). Using structs in these scenarios can significantly improve performance by avoiding heap allocations and GC overhead.

4. The Special Case of string

The string type in C# can be a bit confusing at first. string is a reference type, meaning the text data is stored on the heap. However, strings are also immutable—once a string object is created, it cannot be changed.

When you do something that looks like you're modifying a string, like myString += " world";, you are not changing the original string. Instead, the .NET runtime creates a new string object on the heap with the combined content and updates your variable to reference this new object.

This immutability makes strings behave very much like value types in practice, which is usually the behavior you want. This is another concept that is identical in Java, where String is also an immutable reference type.


Conclusion

In this lesson, we explored one of the most fundamental aspects of the C# language: its type system. Understanding the distinction between value and reference types is not just academic; it has profound implications for performance, memory usage, and correctness.

Here are the key takeaways:

  • Memory Model: C# uses a Stack for fast, temporary storage (like local variables) and a Heap for longer-lived objects.
  • Value Types (struct, int, bool): Store their data directly. When assigned or passed as arguments, their value is copied. They are typically stored on the stack.
  • Reference Types (class, string, array): Store a reference to their data, which lives on the heap. When assigned, the reference is copied, and both variables point to the same object.
  • struct vs. class: This is a key design choice in C#. Use class by default. Use struct for small, data-focused types to optimize performance.
  • string: A special case—it's an immutable reference type, which often makes it behave like a value type.

Now that you have a solid grasp of how C# defines and stores data, our next lesson will look at a C#-specific feature for controlling access to that data: properties. We'll explore how they provide a clean, powerful alternative to the get/set methods you are used to in Java.