Blogs
Understanding Structs

Understanding Structs

When we dive into the world or memory optimizations you’d likely hear the struct keyword. As codebases became larger and I had to start thinking about memory optimizations due to classes becoming large and I notice there were many objects and there were some sets of data being garbage collected - I found myself writing structs when I need to.

When you create a struct, its memory is allocated on the stack. This makes structs more efficient than classes, which are allocated on the heap. This means that structs are more suitable for functions that require high performance and low memory usage. That is cheaper. No garbage collection needed.

On the other hand, classes are allocated on the heap, which means they have no size limit. This makes them more suitable for complex data structures that require a large amount of memory.

Here is another useful doc to read: Link

Keep in mind that if you take a struct and pass it to a function. Change a value in the struct inside that function. Is the value changed after the call to the function? No, you passed a copy of the struct, not a reference. (you CAN pass a reference, but you have to do that explicitly). They behave different from “normal objects” (reference types) under assignment and when passing as arguments, which can lead to unexpected behavior; this is particularly dangerous if the person looking at the code does not know they are dealing with a struct.

Other factors that you might not have in structs are:

  • they cannot be inherited.
  • struct members cannot be specified as abstract, sealed, virtual, or protected.

So structs are pass by value by default. That is a difference.

How to use Structs

C#
1
public struct Point
2
{
3
int x, y;
4
5
public Point(int x, int y)
6
{
7
this.x = x; this.y = y; // OK
8
}
9
}

If we added the following constructor, the struct would not compile, because y would remain unassigned:

C#
1
public Point (int x) { this.x = x; } // Not OK

The Default Constructor

In addition to any constructors that you define, a struct always has an implicit parameterless constructor that performs a bitwise-zeroing of its fields (setting them to their default values):

C#
1
Point p = new Point(); // p.x and p.y will be
2
3
public struct Point
4
{
5
int x, y;
6
}

Even when you define a parameterless constructor of your own, the implicit param‐ eterless constructor still exists and can be accessed via the default keyword

1
Point p1 = new Point(); // p1.x and p1.y will be 1
2
Point p2 = default; // p2.x and p2.y will be 0
3
4
public struct Point
5
{
6
int x = 1;
7
int y;
8
public Point() => y = 1;
9
}

readonly structs

You use the readonly modifier to declare that a structure type is immutable. All data members of a readonly struct must be read-only as follows:

  • Any field declaration must have the readonly modifier
  • Any property, including auto-implemented ones, must be readonly or init only. That guarantees that no member of a readonly struct modifies the state of the struct.
C#
1
// Use init
2
public readonly struct Coords
3
{
4
public Coords(double x, double y)
5
{
6
X = x;
7
Y = y;
8
}
9
10
public double X { get; init; }
11
public double Y { get; init; }
12
13
public override string ToString() => $"({X}, {Y})";
14
}
15
16
// Or use readonly
17
internal readonly struct Details
18
{
19
public readonly string Title;
20
21
public readonly IReadOnlyDictionary<Guid, Employee> PerEmployeeInfo;
22
23
public Details(string title, IReadOnlyDictionary<Guid, Employee> perEmployeeInfo)
24
{
25
// ...
26
}
27
}
28
29
public static void Main()
30
{
31
var p1 = new Coords(0, 0);
32
Console.WriteLine(p1); // output: (0, 0)
33
34
// Non-destructive Mutation:
35
36
var p2 = p1 with { X = 3 };
37
Console.WriteLine(p2); // output: (3, 0)
38
39
var p3 = p1 with { X = 1, Y = 4 };
40
Console.WriteLine(p3); // output: (1, 4)
41
}

ref structs

Ref structs were introduced in C# 7.2 as a niche feature primarily for the benefit of the Span<T> and ReadOnlySpan<T> structs. These structs help with a micro-optimization technique that aims to reduce memory allocations.

Unlike reference types, whose instances always live on the heap, value types live in-place (wherever the variable was declared). If a value type appears as a parameter or local variable, it will reside on the stack:

C#
1
void SomeMethod()
2
{
3
Point p; // p will reside on the stack
4
}
5
6
struct Point { public int X, Y; }

But if a value type appears as a field in a class, it will reside on the heap:

C#
1
class MyClass
2
{
3
Point p; // Lives on heap, because MyClass instances live on the heap
4
}
5
6
struct Point { public int X, Y; }

Adding the ref modifier to a struct’s declaration ensures that it can only ever reside on the stack. Attempting to use a ref struct in such a way that it could reside on the heap generates a compile-time error:

C#
1
var points = new Point[100]; // Error: will not compile!
2
3
ref struct Point
4
{
5
public int X, Y;
6
}
7
8
class MyClass
9
{
10
Point P; // Error: will not compile!
11
}

Real world Application

Here are some examples of how I use structs:

C#
1
public struct Capacity
2
{
3
public List<double> Factors { get; set; }
4
5
public DateTimeOffset ExpiryDate { get; set; }
6
7
public Capacity(List<double> factors, DateTimeOffset expiryDate)
8
{
9
Factors = factors;
10
ExpiryDate = expiryDate;
11
}
12
}
13
14
public readonly struct SeatsInfo
15
{
16
[JsonProperty]
17
public readonly double Total;
18
19
[JsonProperty]
20
public readonly double Available;
21
22
internal SeatsInfo(double total, double available)
23
{
24
// validations...
25
26
Total = total;
27
Available = available;
28
}
29
}
30
31
32
// You use the readonly modifier to declare that a structure type is immutable.
33
// All data members of a readonly struct must be read-only as follows
34
internal readonly struct Details
35
{
36
public readonly string Title;
37
38
public readonly IReadOnlyDictionary<Guid, Employee> PerEmployeeInfo;
39
40
public Details(string title, IReadOnlyDictionary<Guid, Employee> perEmployeeInfo)
41
{
42
// ...
43
}
44
}

I hope this was useful!

x, gio