Struct vs Class - Choosing the Right Type for Your Data in C#

Vaibhav • September 10, 2025

In the previous article, we explored the differences between value types and reference types - how they behave in memory, how they are passed around, and how they affect program correctness. Now, we focus on two specific type declarations in C#: struct and class. Both are used to define custom types, but they differ significantly in behavior, performance, and design intent.

Choosing between a struct and a class is not just a matter of syntax - it’s a design decision that affects how your code performs and how your types behave. In this article, we’ll explore what structs and classes are, how they differ, when to use each, and how to avoid common pitfalls when working with them.

Understanding Structs

A struct in C# is a value type. When you create a struct, its data is stored directly in the variable. Structs are copied by value, which means each copy is independent. Structs are ideal for small, immutable data structures.

public struct Point
{
    public int X;
    public int Y;

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

    public void Move(int dx, int dy)
    {
        X += dx;
        Y += dy;
    }
}

When you assign a Point to another variable, a copy is made:

Point p1 = new Point(1, 2);
Point p2 = p1;
p2.Move(5, 5);

Console.WriteLine(p1.X); // Output: 1
Console.WriteLine(p2.X); // Output: 6

p1 and p2 are completely independent. Modifying p2 does not affect p1.

Structs are stored on the stack (unless boxed) and are not managed by the garbage collector. This makes them faster for short-lived, small data.

Understanding Classes

A class in C# is a reference type. When you create a class instance, the object is stored on the heap, and the variable holds a reference to it. Classes are copied by reference, meaning multiple variables can refer to the same object.

public class Point
{
    public int X;
    public int Y;

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

    public void Move(int dx, int dy)
    {
        X += dx;
        Y += dy;
    }
}

Assigning a class instance copies the reference:

Point p1 = new Point(1, 2);
Point p2 = p1;
p2.Move(5, 5);

Console.WriteLine(p1.X); // Output: 6
Console.WriteLine(p2.X); // Output: 6

Both p1 and p2 refer to the same object. Changes through one variable affect the other.

Key Differences Between Structs and Classes

Structs and classes differ in several important ways:

// Structs
- Value types
- Stored on the stack
- Copied by value
- Cannot inherit from another struct or class
- Implicitly sealed (cannot be inherited)
- No default parameterless constructor (until C# 10)

// Classes
- Reference types
- Stored on the heap
- Copied by reference
- Can inherit from other classes
- Can be abstract or sealed
- Can define parameterless constructors

These differences affect how your types behave in memory, how they interact with other code, and how they perform.

Structs are best for small, immutable types. Classes are better for complex, mutable, or polymorphic types.

When to Use a Struct

Use a struct when:

  • The type represents a small, simple value (like a coordinate or color).
  • The type is immutable or changes infrequently.
  • Performance is critical and you want to avoid heap allocation.
  • You don’t need inheritance or polymorphism.

Examples of good struct candidates include:

struct Point
struct Size
struct DateTime
struct Guid

These types are small, self-contained, and copied by value.

When to Use a Class

Use a class when:

  • The type has complex behavior or mutable state.
  • You need inheritance or interfaces.
  • The type is large or used in many places.
  • You want reference semantics (shared access).

Examples of good class candidates include:

class User
class Order
class Logger
class Repository

These types often manage resources, collaborate with other objects, and benefit from reference semantics.

Immutability and Structs

Structs are often used for immutable types. This means their fields cannot be changed after construction. You can enforce immutability by making fields readonly and using init properties.

public struct Currency
{
    public decimal Amount { get; init; }
    public string Code { get; init; }

    public Currency(decimal amount, string code)
    {
        Amount = amount;
        Code = code;
    }
}

This struct is immutable. Once created, its values cannot be changed. This makes it safe to copy and use in concurrent scenarios.

Design structs to be immutable. Mutable structs can lead to confusing bugs, especially when used in collections or passed by value.

Performance Considerations

Structs can be more efficient than classes because they avoid heap allocation and garbage collection. However, copying large structs can be expensive. Use structs for small types (typically under 16 bytes).

Classes are better for large or complex types. They allow shared access and reduce copying overhead.

// Good struct
struct Point { int X; int Y; }

// Avoid large struct
struct BigData
{
    public int[] Values; // Reference type inside struct
    public string Name;
}

Large structs can behave unpredictably and hurt performance. Prefer classes for such scenarios.

Structs in Collections

When using structs in collections like List<T>, be aware that structs are copied by value. Modifying a struct inside a collection requires reassigning it.

List<Point> points = new List<Point>();
points.Add(new Point(1, 2));

Point p = points[0];
p.X = 10;
points[0] = p; // Must reassign to update

Failing to reassign can lead to silent bugs. Always treat structs in collections carefully.

Summary

Structs and classes are both powerful tools for defining custom types in C#. Structs are value types - lightweight, copied by value, and ideal for small, immutable data. Classes are reference types - flexible, shared by reference, and suited for complex, mutable, or polymorphic behavior.

We explored how structs and classes differ in memory, behavior, and design. We discussed when to use each, how to enforce immutability, and how to avoid common pitfalls. We also covered performance considerations and how structs behave in collections.

As you continue building applications, choose structs for small, simple, immutable data. Choose classes for rich, shared, and extensible behavior. Understanding the trade-offs will help you write efficient, maintainable, and correct code.

In the next article, we’ll explore Object Equality - how to compare objects correctly using value equality, reference equality, and custom comparison logic.