5 min read

Structs

Crash Course

Introduction

A struct is a concept similar to a class. The difference is that a class defines a new reference type, while a struct defines a new value type.

We’ve discussed value and reference types before, but let’s refresh our memories. A variable whose type is a value type stores the data in the memory location directly. A variable whose type is a reference type stores the data in another place (the heap, but that’s a conversation for a different day) and the variable itself holds only a reference to the data, which it can use to look it up as needed.

This one minor difference has a huge impact. For example, an int is a value type, while a string is a reference type. Consider the following:

int a = 3;
int b = a;

string c = "Hello World!";
string d = c;

When we assign a to b, the contents of a are copied to b. Remember: the contents of a value type, like int, is the data itself. That means b = a will copy the bits and bytes that represent the number 3 from a to b.

Meanwhile, when we assign c to d, again, the contents of c are copied to d. But in this case, a reference is copied, not the raw data. We do not end up with two totally distinct strings containing the text "Hello World!". Rather, we end up with one copy of that data in memory, and two variables that each have a reference to it.

Now suppose we have a simple class like this:

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

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

And we do this:

Point e = new Point(0, 5);
Point f = e;

Because Point is a class, the same thing happens here as we saw with strings. e and f both end up with a reference that takes them to the same object in memory, rather than two variables that contain their own, complete copy of the Point3.

Where this might get surprising is if you do this next:

e.Y = -5;
Console.WriteLine(f.Y);

What will this display?

Since e and f both have a reference to the same object in memory, a change made to that memory location via e will be visible through f.

(We also see this happen when we pass data to a parameter through a method call.)

We’ve seen how classes let us define new custom reference types. How could we define our own custom value types?

That’s where structs come in to play.

Defining a Struct

We can define a new struct, which is similar to a class in many ways, but is a custom value type instead of a reference type.

The syntax is almost identical to a class, just with using the struct keyword instead of the class keyword:

struct Point2
{
    public float X { get; set; }
    public float Y { get; set; }

    public Point2(float x, float y)
    {
        X = x;
        Y = y;
    }
}

Point2 is a struct, so it is a value type. But otherwise, this struct definition is identical to the class version.

But consider:

Point2 g = new Point2(0, 5);
Point2 h = g;

g.Y = -5;
Console.WriteLine(h.Y);

When we assign g to h, a copy of all of the data will be moved to h. The whole struct value is copied, not a reference.

Which means that g.Y and h.Y are different locations in memory. When we change g.Y, it will affect g, but not affect the totally different copy that is in h, and we’ll output 5.

Differences between a Class and a Struct

The main difference between a class and a struct is the value vs. reference type distinction. As we just saw, copying value types is quite different from copying reference types. But there are other differences.

For example, a struct cannot use inheritance, though they can implement interfaces.

Both structs and classes have an automatic parameterless constructor, but for a class, when you add in your own constructor, the automatic one is removed. For a struct, the parameterless constructor stays. In fact, for a class, you can supply your own parameterless constructor, but for a struct, you can’t. There is no way to get rid of that default constructor.

Additionally, for a class, all fields in a class are automatically initialized to their default value (0 or the closest equivalent, like false for a bool) but when you make a constructor for a struct, you must assign a value to all fields in the struct. That is because a struct doesn’t automatically assign a value to its fields.

The other big difference is that two reference types, such as a class, are considered equal if the references point to the exact same object in memory. In contrast, value types are equal if all of their data is the same.

So the following will display false:

Point a = new Point(0, 0);
Point b = new Point(0, 0);
Console.WriteLine(a == b); // References to two different objects, so not equal.

But this will display true:

Point2 c = new Point2(0, 0);
Point2 d = new Point2(0, 0);
Console.WriteLine(a == b); // Contain the same data, therefore, they are equal.

When to Use a Struct

Let’s talk about when to make a struct instead of a class.

The name struct should give us a hint about its usage: struct is short for structure. A struct makes the most sense when you have a small type that is primarily data-focused. The Point/Point2 thing is a good example. It is a small bundle of data without a lot of behavior. It is like an int, just with a bit more data.

You don’t want to use a struct if it will hold a lot of data. Because every time you use it, you’re copying all of those bytes around, if the struct contains a lot of fields, it will become slow to make all of those copies.

tip

You will probably make far more classes than you do structs. It isn’t entirely unreasonable to just pretend structs aren’t an option, and just always make a class.

The reason to bring it up in this crash course is that every game development framework, engine, or library will have a few structs scattered here and there. If you don’t understand the differences with how they work, you’ll make lots of mistakes (and have a very hard time understanding why) with how they’re used.