Skip to content

Intermediate · Classes and objects · topic 17 of 43 · 30 min

Structs, value types and reference types

Explanation

C# has two kinds of types. Value types (int, double, decimal, bool, DateTime and your own structs) hold their data directly, so assigning copies the whole value. Reference types (classes, strings, arrays) hold a reference, so assigning shares the same object. Use a struct for small, simple, value-like data; use a class for almost everything else.

Two kinds of types

Every type in C# is either a value type or a reference type. The difference decides what happens when you copy a variable, pass it to a method, or compare it. Interviewers ask about this very often.

Value typeReference type
Examplesint, double, decimal, bool, char, DateTime, enum, structclass, string, array, List<T>, delegate
Variable holdsThe data itselfA reference (address) to an object
Assignment b = aCopies the whole valueCopies the reference; both share one object
Can be null?No (unless written as int?)Yes
Default value0, false, or all fields zeronull
Think of it like this: A value type is like a photocopy of your Aadhaar card: give someone a copy and whatever they scribble on it, your copy is fine. A reference type is like sharing a Google Docs link: everyone with the link edits the same document.

Seeing the difference

csharp
PointS s1 = new PointS { X = 1, Y = 2 };
PointS s2 = s1;          // copies the values
s2.X = 10;
Console.WriteLine(s1.X); // 1

PointC c1 = new PointC { X = 1, Y = 2 };
PointC c2 = c1;          // copies the reference
c2.X = 10;
Console.WriteLine(c1.X); // 10

struct PointS { public int X; public int Y; }
class PointC { public int X; public int Y; }
Same fields, different behaviour. Only the keyword struct versus class changed.

Stack and heap: the honest, simple version

You will hear "value types live on the stack, reference types live on the heap". It is a useful first picture but not the full truth. The stack is fast memory for a method's local variables; the heap is memory managed by the garbage collector. A local int usually sits on the stack. Every class object lives on the heap. But an int field inside a class object lives on the heap, inside that object. So think in terms of copy versus share, which is always true, rather than stack versus heap, which depends on where the value is stored.

Writing your own struct

csharp
var a = new Money(100m, "INR");
var b = a.Add(new Money(50.5m, "INR"));
Console.WriteLine(b);   // INR 150.50

public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public Money Add(Money other) => new Money(Amount + other.Amount, Currency);
    public override string ToString() => $"{Currency} {Amount:F2}";
}
readonly struct makes every field read-only, so a Money value never changes; methods return a new value instead. Immutable structs avoid many surprises.

When to choose a struct

  • It is small (a few numbers), roughly 16 bytes or less.
  • It represents a single value, like a point, a money amount, a colour or a date.
  • It is immutable (never changes after creation).
  • It is created in very large numbers, where avoiding heap objects helps performance.

If any of these is false, use a class. Most of your types in business apps will be classes. C# also has record struct (module 5), which is often the easiest way to write a small value type.

Boxing and unboxing

Boxing happens when a value type is stored as object (or an interface): .NET makes a copy of it inside a new heap object, a "box". Unboxing is the cast back to the value type. It works, but it costs time and memory in hot loops. Generic collections like List<int> avoid boxing; the old ArrayList boxed every int.

csharp
int n = 42;
object boxed = n;          // boxing: a copy goes into a heap object
int back = (int)boxed;     // unboxing
n = 7;
Console.WriteLine(boxed);  // 42 (the box holds its own copy)
Unboxing to the wrong type, like (long)boxed, throws InvalidCastException.

Equality

For classes, == checks whether two references point to the same object (unless the class defines its own ==, as string does). For your own structs, == does not compile until you write the operator yourself, while Equals compares the field values. Records (module 5) give you value equality for free.

Common mistakes and fixes

  • Changing a struct inside a List: list[0].X = 5 is error CS1612, because list[0] returns a copy. Read it, change the copy, and store it back, or better, make the struct immutable.
  • Big mutable structs passed around: every pass copies all the fields. Make it a class.
  • Expecting a method to change a struct argument: it gets a copy. Return the new value instead.
  • Thinking string is a value type because it behaves like one: string is a reference type that is immutable and compares by content.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Structs, value types and reference types · 1/7

Value vs reference

In this lesson you will learn the most important difference in C#: value types versus reference types.

Prefer a video by a teacher? Find free videos on “C# value types vs reference types struct vs class boxing explained” on YouTube ↗

In real life: GPS points in a cab app

A cab-hailing app handles millions of location updates from drivers every minute. Each location is just a latitude and a longitude, never changed after it is read, so a small readonly struct is a good fit and avoids creating millions of heap objects.

csharp
var pickup = new GeoPoint(12.9716, 77.5946);   // Bengaluru
var drop = pickup.Move(0.01, 0.0);
Console.WriteLine(pickup);
Console.WriteLine(drop);

public readonly struct GeoPoint
{
    public double Lat { get; }
    public double Lng { get; }
    public GeoPoint(double lat, double lng) { Lat = lat; Lng = lng; }
    public GeoPoint Move(double dLat, double dLng) => new GeoPoint(Lat + dLat, Lng + dLng);
    public override string ToString() => $"({Lat:F4}, {Lng:F4})";
}
Shows (12.9716, 77.5946) then (12.9816, 77.5946). pickup is unchanged because Move returns a new value.

Practice (0/4 done)

Task 1 · Question

Which of these is a value type?

Task 2 · Question

What does this print? var a = new P { V = 1 }; var b = a; b.V = 2; Console.WriteLine(a.V); struct P { public int V; }

Task 3 · Question

int n = 5; object o = n; n = 9; Console.WriteLine(o); What is printed?

Task 4 · Practice

Write a readonly struct Temperature with a double Celsius property, a constructor, a method ToFahrenheit() returning double (C * 9 / 5 + 32), and a method Warmer(double by) that returns a NEW Temperature. Show that the original does not change.

Quick quiz

Question 1

1.Assigning one class variable to another...

Question 2

2.Which statement is most accurate?

Question 3

3.What is boxing?

Question 4

4.When is a struct a good choice?

Question 5

5.What is the default value of a reference-type field?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Assigning one class variable to another...

Answer: Copies the reference, so both refer to the same object

Classes are reference types.

Which statement is most accurate?

Answer: Value types are copied on assignment; where they are stored depends on context

A value-type field inside a class object lives on the heap. Copy semantics is the reliable rule.

What is boxing?

Answer: Wrapping a value type in a heap object when it is treated as object

Boxing makes a heap copy of a value type.

When is a struct a good choice?

Answer: For small, immutable, value-like data such as a point or money

Small, immutable values are the classic struct use.

What is the default value of a reference-type field?

Answer: null

Reference-type fields default to null.

Is record struct a value type or a reference type?Related

Answer: Value type

record struct (C# 10) is a value type.

From Records and pattern matching

Can a struct contain a member of its own struct type (not a pointer)?Related

Answer: No: it would be infinitely large; it can contain a pointer to its type

Self-reference must be through a pointer.

From Pointers to structs and the arrow operator

Which type holds true or false?Related

Answer: bool

bool has exactly two values: true and false.

From Types and variables

What does assigning one struct variable to another do?Related

Answer: Copies all the fields

Structs are values; assignment copies every field.

From Structs and methods

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Classes and objects”. The module exam comes next.
  2. Module 4 exam: Classes and objects · 13 questions · 20 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Inheritance, interfaces and records. 43 topics left in the course before the final exam.

← Previous: Access modifiers, static members and encapsulation