Understanding Structs in C#

A guide to defining immutable value types in C#

Posted by Hüseyin Sekmenoğlu on January 02, 2019 Programming Fundamentals

Structs in C# provide a lightweight way to define value types. Unlike classes, which are reference types, structs are copied by value, making them useful for scenarios where data integrity is crucial and mutation should be avoided.


🔍 What Is a Struct?

In C# a struct is a value type. This means that when a struct is assigned to a new variable, a copy is made. Built-in types like int, bool and decimal are all structs. However, string and object are reference types, not value types.

You can create custom value types using the struct keyword, following a syntax similar to class or interface.

struct Angle
{
    public Angle(int degrees, int minutes, int seconds)
    {
        Degrees = degrees;
        Minutes = minutes;
        Seconds = seconds;
    }

    public int Degrees { get; }
    public int Minutes { get; }
    public int Seconds { get; }

    public Angle Move(int degrees, int minutes, int seconds)
    {
        return new Angle(
            Degrees + degrees,
            Minutes + minutes,
            Seconds + seconds
        );
    }
}

The Angle struct above is immutable: all its properties are read-only.


🔒 Immutability in Structs

C# 6.0 introduced read-only auto-properties, simplifying immutable type creation. C# 7.2 added the readonly struct declaration, allowing the compiler to enforce immutability.

readonly struct Angle
{
    // compiler checks immutability
}

🔹 Why prefer immutability in structs?

  • Predictability: Value types should represent values that do not change.

  • Clarity: Because structs are copied, mutating one won't affect another.

  • Safety: Avoids hard-to-find bugs caused by unexpected changes.


📏 Guidelines for Structs

  • DO make structs immutable.

  • DO define methods like Move() that return new instances.

  • DO avoid setters in structs.

  • DO favor auto-implemented read-only properties.

  • DON'T define a parameterless constructor (C# generates one).

  • DON'T use field initializers inside structs.


🛠️ Initializing Structs

Unlike classes, structs can't have field initializers and every constructor must initialize all fields. Here's an example that does not compile:

struct Angle
{
    // ❌ Not allowed in structs
    // int _Degrees = 42;
}

And an example that compiles:

struct Angle
{
    public Angle(int degrees, int minutes, int seconds)
    {
        _Degrees = degrees;
        _Minutes = minutes;
        _Seconds = seconds;
    }

    public int Degrees { get { return _Degrees; } }
    private readonly int _Degrees;

    public int Minutes { get { return _Minutes; } }
    private readonly int _Minutes;

    public int Seconds { get { return _Seconds; } }
    private readonly int _Seconds;
}

⛔ Using this before initializing all fields leads to a compile error. Always initialize fields before using properties or methods that reference them.


📌 Important Notes

  • The default value of a struct is always the zeroed version of its fields.

  • Structs may contain methods, constructors and properties.

  • When using arrays or classes that include structs, C# initializes them using default values.


✅ Best Practices Summary

Do

Don't

Create immutable structs

Use field initializers

Use auto-implemented read-only properties (C# 6.0+)

Define custom parameterless constructors

Ensure constructors fully initialize fields

Use this before full initialization

Prefer value semantics over reference semantics

Assume shared behavior like classes


🌐 Real-World Usage Example

class Coordinate
{
    public Angle Longitude { get; set; }
    public Angle Latitude { get; set; }
}

If Angle were a class, changes in one instance would affect all references. As a struct, each Coordinate has its own independent copy of Angle.