Structs (struct vs class)
Reviewed & published by Brayan K
A struct looks almost identical to a class, but there's one profound difference: a struct is a value type. It's copied when you assign or pass it, instead of shared by reference. Understanding value vs reference semantics is one of the most important things you'll learn in C# — it explains a whole category of "why did that change?!" bugs.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Declare a struct with fields and methods
- Value vs reference semantics (copy vs share)
- Why structs are copied on assignment
- readonly structs for safe immutability
- record struct for value equality
- When to pick a struct over a class
💡 Real-World Analogy
Imagine handing a colleague a document. With a class, you share a single Google Doc — they edit it and your copy changes too, because it's the same document. With a struct, you photocopy the document and hand them the copy — they can scribble all over it and your original is untouched. That photocopy-on-handover behaviour is exactly what "value type" means.
1. Declaring a Struct
A struct can hold fields, a constructor, properties and methods — syntactically it's nearly a class. It's ideal for small bundles of data that represent a single value, like a 2D point. The classic example is Point: two coordinates plus a little behaviour.
using System;
// A struct groups related values into one type — like a class, but it's
// a VALUE type (copied when assigned, lives on the stack).
struct Point
{
public int X;
public int Y;
public Point(int x, int y) { X = x; Y = y; }
public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
}
class Program
{
static void Main()
{
var p = new Point(3, 4);
Console.WriteLine($"({p.X}, {p.Y})"); // (3, 4)
Console.WriteLine(p.DistanceFromOrigin()); // 5
}
}
// ✅ Expected output:
// (3, 4)
// 52. Value vs Reference Semantics
This is the whole point of the lesson. When you assign a struct to a new variable, you get an independent copy — change one and the other is unaffected. When you assign a class, both variables point at the same object — change one and you change "both". Run this side-by-side comparison and let it sink in.
using System;
struct PointStruct { public int X; public int Y; } // VALUE type
class PointClass { public int X; public int Y; } // REFERENCE type
class Program
{
static void Main()
{
// STRUCT: assigning makes a full COPY. Changing the copy
// does NOT affect the original.
var s1 = new PointStruct { X = 1, Y = 1 };
var s2 = s1; // copy
s2.X = 99;
Console.WriteLine($"struct: s1.X={s1.X}, s2.X={s2.X}"); // s1.X=1, s2.X=99
// CLASS: assigning copies the REFERENCE — both point at one object.
// Changing one changes "both", because they're the same object.
var c1 = new PointClass { X = 1, Y = 1 };
var c2 = c1; // same object
c2.X = 99;
Console.WriteLine($"class: c1.X={c1.X}, c2.X={c2.X}"); // c1.X=99, c2.X=99
}
}
// ✅ Expected output:
// struct: s1.X=1, s2.X=99
// class: c1.X=99, c2.X=99Your turn — complete a temperature struct by writing a computed property.
using System;
struct Temperature
{
public double Celsius;
// 🎯 YOUR TURN — fill in the blanks (replace each ___).
// 1) A read-only property that converts Celsius to Fahrenheit.
// Formula: F = C * 9 / 5 + 32
public double Fahrenheit => ___; // 👉 Celsius * 9 / 5 + 32
public Temperature(double c) { Celsius = c; }
}
class Program
{
static void Main()
{
var t = new Temperature(25);
// 2) Print the Fahrenheit value
Console.WriteLine(___); // 👉 t.Fahrenheit
// ✅ Expected output: 77
}
}3. readonly Structs
Mutable structs are a known foot-gun: because they copy so freely, a "change" you make might land on a copy you didn't mean to touch. The modern best practice is to make structs readonly so they're immutable — operations return a new value instead of mutating in place. This removes the surprise entirely.
using System;
// 'readonly struct' guarantees the struct can't be mutated after creation —
// every field must be readonly. This is the recommended modern style.
readonly struct Money
{
public readonly decimal Amount;
public readonly string Currency;
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
// Operations return a NEW value rather than mutating this one:
public Money Add(Money other) => new Money(Amount + other.Amount, Currency);
public override string ToString() => $"{Amount:F2} {Currency}";
}
class Program
{
static void Main()
{
var wallet = new Money(10.00m, "GBP");
var after = wallet.Add(new Money(5.50m, "GBP"));
Console.WriteLine(wallet); // 10.00 GBP (unchanged)
Console.WriteLine(after); // 15.50 GBP
}
}
// ✅ Expected output:
// 10.00 GBP
// 15.50 GBP4. record struct
If you want value-copy semantics and the free value equality, ToString(), and deconstruction of a record, use a record struct. A readonly record struct is the ideal small immutable value type — it's exactly what types like Vector or Color should be.
using System;
// 'record struct' = value type + the free equality/ToString of a record.
// Best of both worlds for small immutable data.
readonly record struct Vector(double X, double Y)
{
public double Length => Math.Sqrt(X * X + Y * Y);
}
class Program
{
static void Main()
{
var a = new Vector(3, 4);
var b = new Vector(3, 4);
Console.WriteLine(a == b); // True (value equality, generated)
Console.WriteLine(a); // Vector { X = 3, Y = 4 }
Console.WriteLine(a.Length); // 5
}
}
// ✅ Expected output:
// True
// Vector { X = 3, Y = 4, Length = 5 }
// 5🧩 Reorder Challenge
These lines build and use a struct, but they're scrambled. Order them so the program compiles and prints the area.
A. public int Area() => Width * Height;
B. struct Rect {
C. var r = new Rect(4, 5);
D. public Rect(int w, int h) { Width = w; Height = h; }
E. Console.WriteLine(r.Area());
F. public int Width; public int Height;
G. }B. struct Rect {
F. public int Width; public int Height;
D. public Rect(int w, int h) { Width = w; Height = h; }
A. public int Area() => Width * Height;
G. }
// ...inside Main:
C. var r = new Rect(4, 5);
E. Console.WriteLine(r.Area());
// Output: 20Fields, then constructor, then methods make up the struct body; you must construct r before calling Area() on it.
🧠 Quick Recall
1. What does this print?
struct S { public int N; }
var a = new S { N = 1 };
var b = a;
b.N = 5;
Console.WriteLine(a.N);1 — b is a copy, so changing b.N leaves a.N at 1.
2. What does this print?
class C { public int N; }
var a = new C { N = 1 };
var b = a;
b.N = 5;
Console.WriteLine(a.N);5 — a and b reference the same object, so the change is shared.
3. What does this print?
record struct V(int X);
Console.WriteLine(new V(2) == new V(2));True — record structs get generated value equality.
struct vs class — Decision Guide
| struct | class | |
|---|---|---|
| Kind | Value type | Reference type |
| On assignment | Copied | Shared (reference) |
| Lives on | Stack (usually) | Heap |
| Can be null | No (unless T?) | Yes |
| Inheritance | No | Yes |
| Best for | Small immutable values | Objects with identity/behaviour |
Pro Tips
- 💡 Default to classes; reach for structs deliberately when you have small, immutable, value-like data.
- 💡 Make structs readonly to avoid the mutable-struct copy bugs entirely.
- 💡 Keep structs small (~16 bytes). Large structs are expensive to copy on every assignment and pass.
- 💡 readonly record struct is the sweet spot for things like points, colours, and money.
Common Errors (and the fix)
- "My change disappeared!" — you mutated a struct that was actually a copy (e.g. an item fetched from a list). Make the struct readonly and return new values.
- "CS0843: auto-property must be fully assigned": a struct constructor must assign every field/auto-property before it finishes.
- Trying to inherit a struct: structs can't inherit. Use composition or an interface instead.
- Big struct, slow code: copying a large struct everywhere hurts performance. If it's big or long-lived, it should probably be a class.
Mini-Challenge: an RGB Color Struct
Build a small immutable value type that represents a colour and can print itself as a hex string. This is a textbook real-world use of a readonly struct.
using System;
class Program
{
static void Main()
{
// 🎯 MINI-CHALLENGE: a RGB Color struct
// 1. Above Program, declare: readonly struct Color { ... }
// with byte fields R, G, B and a constructor (byte r, byte g, byte b).
// 2. Add a method ToHex() that returns a string like "#FF8800"
// Hint: $"#{R:X2}{G:X2}{B:X2}" (X2 = 2-digit uppercase hex)
// 3. Create new Color(255, 136, 0) and print ToHex().
//
// ✅ Example output:
// #FF8800
// your code here
}
}🎉 Lesson Complete
- ✅ Structs are value types — copied on assignment
- ✅ Classes are reference types — shared by reference
- ✅ Copy vs share explains a whole class of "why did that change?" bugs
- ✅ Prefer readonly structs for safe immutability
- ✅ readonly record struct = value copy + value equality
Practice quiz
A struct is what kind of type?
- A reference type
- An interface
- A value type
- An abstract type
Answer: A value type. A struct is a value type - copied when assigned or passed - unlike a class, which is a reference type.
What happens when you assign one struct variable to another?
- You get an independent copy
- Both share the same object
- It throws an exception
- The original becomes null
Answer: You get an independent copy. Assigning a struct makes a full copy; changing the copy does not affect the original.
Given 'class C', after var b = a; b.X = 99; what is a.X (where a.X was 1)?
- 1
- 0
- null
- 99
Answer: 99. Classes are reference types - a and b point at the same object, so the change is shared and a.X is 99.
What does declaring a 'readonly struct' guarantee?
- It lives on the heap
- It can't be mutated after creation - every field is readonly
- It can be inherited
- It can be null
Answer: It can't be mutated after creation - every field is readonly. A readonly struct is immutable; operations return a new value instead of mutating in place.
Why are mutable structs discouraged?
- Because structs copy so freely, a 'change' may land on a copy you didn't mean to touch
- They're slower to allocate
- They can't have methods
- They always box
Answer: Because structs copy so freely, a 'change' may land on a copy you didn't mean to touch. Easy copying means mutations can hit an unintended copy; making structs readonly removes the surprise.
What extra features does a 'record struct' add over a plain struct?
- Inheritance support
- Heap allocation
- Generated value equality, ToString(), and deconstruction
- Null support
Answer: Generated value equality, ToString(), and deconstruction. A record struct is still a value type but the compiler generates value equality, ToString(), and deconstruction.
Can a struct inherit from another struct or class?
- Yes, like classes
- No - structs can't be the base for or derive from inheritance
- Only from interfaces' base classes
- Only from abstract structs
Answer: No - structs can't be the base for or derive from inheritance. Structs can implement interfaces and have methods, but they cannot participate in inheritance.
What does a struct constructor have to do before it finishes?
- Call base()
- Return a value
- Mark itself readonly
- Assign every field / auto-property
Answer: Assign every field / auto-property. CS0843 occurs if a struct constructor doesn't fully assign every field/auto-property before completing.
Roughly what size is a struct generally recommended to stay under?
- 1 byte
- 16 bytes
- 256 bytes
- 1 KB
Answer: 16 bytes. Microsoft's guideline is small (~16 bytes), immutable, and short-lived; larger data is expensive to copy.
Can a non-nullable struct variable be null?
- Yes, always
- Only in classes
- No, unless you use a nullable type like T?
- Only when readonly
Answer: No, unless you use a nullable type like T?. Value types can't be null by default; you need a nullable value type (T?) to allow null.
Continue this course
- Previous: Records
- Next: Tuples & Deconstruction — Return multiple values and deconstruct them
- Quick reference: C# cheat sheet
Frequently asked questions
What's the core difference between a struct and a class?
A struct is a value type: it's copied when you assign or pass it, and it typically lives on the stack. A class is a reference type: variables hold a reference to one shared object on the heap. Copy semantics is the difference that drives everything else.
When should I use a struct?
Use a struct for small, simple, immutable data that behaves like a single value — points, colours, money amounts, coordinates. Microsoft's guideline is: under ~16 bytes, immutable, and short-lived. For anything larger or with identity, use a class.
Can a struct have methods and properties?
Yes. Structs can have constructors, methods, properties, and implement interfaces — just like classes. What they can't do is inherit from another struct or class (other than ValueType/object), and they can't be the base for inheritance.
Why are mutable structs discouraged?
Because structs copy so easily, mutating one often changes a copy you didn't expect to change, leading to subtle bugs. The fix is to make structs 'readonly' so they're immutable — then copies can never surprise you.
What's the difference between a record struct and a regular struct?
A 'record struct' is still a value type but the compiler also generates value equality, ToString(), and deconstruction for you — the same conveniences as a record class, without the heap allocation.