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

💡 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)
//    5

2. 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=99

Your 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 GBP

4. 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:  20

Fields, 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

structclass
KindValue typeReference type
On assignmentCopiedShared (reference)
Lives onStack (usually)Heap
Can be nullNo (unless T?)Yes
InheritanceNoYes
Best forSmall immutable valuesObjects with identity/behaviour

Pro Tips

Common Errors (and the fix)

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

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

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.