Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I'll be honest with you: for about 90% of what you do in C#, a class is the right choice. It's the workhorse. But as you start building more complex systems—especially if you get into game dev, high-frequency trading, or heavy data processing—you'll run into a performance wall. That's where structs come in.

The fundamental difference is that a class is a reference type (stored on the heap) and a struct is a value type (usually stored on the stack). In plain English: a class is like a shared Google Doc—everyone has a link to the same file. A struct is like a Word document you email to someone—they get their own independent copy.

Starting with a simple Point class

Let's imagine we're building a basic 2D map system. We need a way to represent coordinates. My first instinct is usually to just throw everything in a class.

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

This looks fine. It works. But watch what happens when I try to move a point using a method. I'm expecting the method to calculate a new position, but I don't want it to accidentally mess with my original starting point.

var startPoint = new Point(0, 0);
MovePoint(startPoint);
Console.WriteLine($"Start is now: {startPoint.X}, {startPoint.Y}"); 

void MovePoint(Point p)
{
    p.X += 10;
    p.Y += 10;
}
// Output: Start is now: 10, 10

Wait, why did my start point move?

This is the "Reference Type" trap. When I passed startPoint into MovePoint, I didn't pass the data itself; I passed a reference (a pointer) to the object on the heap. The method then reached into that memory address and changed the values. For a coordinate, this is often not what we want. A point is a value, not an entity with a complex lifecycle.

Let's swap class for struct and see how the behavior shifts.

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

If we run that same MovePoint logic now, the output is Start is now: 0, 0. Why? Because C# copied the entire value of the struct when it was passed into the method. The method modified a local copy on the stack, and the original remained untouched. This is much more predictable for small data types.

My mistake: The Mutable Struct Nightmare

Now, here is where I messed up early in my career. I thought, "Great, it's a struct, but I still want to be able to change X and Y easily." I left the properties as set. This creates a massive headache when you start using structs in lists or arrays.

If you have a List<Point> and you try to do myList[0].X = 10;, the compiler will actually throw an error. This is because the indexer returns a copy of the struct. If C# let you change the X value on that copy, the change would vanish instantly, and you'd be wondering why your data isn't updating. It's a silent bug waiting to happen.

The fix? Always make your structs immutable. Use readonly struct and init properties. If you need to change a point, you don't modify it—you create a new one.

public readonly struct Point
{
    public int X { get; init; }
    public int Y { get; init; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Now, if I want to "move" a point, I'd do something like var newPoint = new Point(oldPoint.X + 10, oldPoint.Y + 10);. It's cleaner, thread-safe, and avoids the weird pitfalls of value-type copying.

When to actually use which?

  • Use a Struct when: The object is small (usually under 16 bytes), logically represents a single value (like a color, a coordinate, or a complex number), and will not be changed after it's created.
  • Use a Class when: The object has a "personality" or identity, needs to be inherited from, is large, or needs to be passed around and modified by multiple parts of your program.



📋 Practical Task

Refactoring the Vector2D Value Type

You are working on a physics engine. Currently, the Vector2D is implemented as a class, which is causing memory pressure (too many objects on the heap) and accidental data mutation bugs during collision calculations.

Your Task:

  1. Convert the Vector2D class into a readonly struct.
  2. Change the properties from set to init to ensure immutability.
  3. Implement a method called Add that takes another Vector2D and returns a new Vector2D representing the sum of the two, rather than modifying the existing one.
// Current buggy implementation
public class Vector2D
{
    public double X { get; set; }
    public double Y { get; set; }

    public Vector2D(double x, double y)
    {
        X = x;
        Y = y;
    }
    
    public void Add(Vector2D other)
    {
        this.X += other.X;
        this.Y += other.Y;
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.