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 |
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
.