Structs
The Crash Course
- Structs are very similar to classes. They can be created using the struct keyword instead of the class keyword.
- Structs are created on the stack instead of the heap, making them faster, performance-wise, but it also means that they are passed by value instead of by reference.
Introduction
With an understanding of classes behind us, let's talk about a similar construct: the struct.
The name struct comes from the word "structured" and comes from C++ (which in turn got it from C). It represents a "structured" set of data or a "record". Structs in C were the beginnings of object-oriented programming and ultimately led to classes in C++, which were the basis for C#.
Structs are structurally very similar to a class. At first glance, it looks almost identical. But there is a key difference. A class defines a new reference type. As we saw earlier, reference types are allocated on the heap and you pass around references to them. A struct defines a new value type. These are types that do not have references, and passing them around leads to duplicating the memory for the thing instead.
Creating a Struct
So far, most things we've done with classes can also be done with structs. In fact, compare these two type definitions, the first of which is a struct and the second of which is a class:
struct TimeStruct { public int Seconds { get; set; } public int CalculateMinutes() { return Seconds / 60; } } class TimeClass { public int Seconds { get; set; } public int CalculateMinutes() { return Seconds / 60; } }
Aside from the struct vs. class keyword, the two look identical. As mentioned a moment ago, the main difference is whether you've created a value type or a reference type. If it is a struct, it will be a value type. If it is a class, it will be a reference type. That can be a tricky nuance to understand and remember, so let's consider an example or two.
TimeStruct s = new TimeStruct(); s.Seconds = 120; UpdateAndDisplayStruct(s); TimeClass c = new TimeClass(); c.Seconds = 120; UpdateAndDisplayClass(c); static void UpdateAndDisplayStruct(TimeStruct timeStruct) { timeStruct.Seconds += 10; Console.WriteLine(timeStruct.CalculateMinutes()); } static void UpdateAndDisplayClass(TimeClass timeClass) { timeClass.Seconds += 10; Console.WriteLine(timeClass.CalculateMinutes()); }
Because TimeClass is a reference type, when we pass c into UpdateAndDisplayClass, we copy the reference out of c and into the timeClass parameter. The main method and UpdateAndDisplayClass both have references to an object on the heap that they can both see and work with. Thus, when we modify the object in UpdateAndDisplayClass, it will affect the shared object, and the main method would be able to tell that it was changed.
In contrast, TimeStruct is a value type. When we pass s into UpdateAndDisplayStruct, we copy the entire chunk of data that is the TimeStruct, and we make a complete copy. UpdateAndDisplayStruct's timeStruct parameter will have a copy of the data, and when it modifies it, it will only modify its own copy.
Thus, we tend to get references to shared objects with classes, but with structs, that's not an option.
This is ignoring a fair bit of nuance. For example, a struct can have members, including fields and properties, that are reference types themselves (a struct can be made up of multiple reference types), and a class can have members that are value types (that part we've already seen a bunch). It gets a bit more tricky when you've got things intermixed like that because the struct may get copied, but if the struct contains references, those references will get duplicated, and we'll have duplicate references to the same objects. (Did I mention it was complicated?)
Structs are the simpler type, but classes are more powerful. Structs should generally be used when you want some small thing that is data-focused and relatively small. The size matters because structs will get copied around in all sorts of places, including passing one to a method, as we've seen here. If you have a struct with seventeen different members, that's a lot of data to copy. For example, if you want to represent a 2D point, making a Point struct may actually make more sense than a Point class. It will likely only have two fields (one for the x-coordinate and one for the y-coordinate) and is data-focused.
If you have a lot of behavior, a class is typically better than a struct.
In truth, most C# programmers use classes 99.9% of the time and almost never use structs. However, you will encounter structs quite often, so it is valuable to be aware of them and understand how they differ from a class.
What's Next?
Structs are conceptually similar to a class, with the main difference being that it is a value type instead of a reference type. You won't get references to a struct value (without some tricks and gimmicks). They're ideal for defining small, data-focused types but are otherwise not nearly as common as classes.
The next thing for us to look at is some of the more advanced features of classes. These features give us a whole lot of power in how we reuse code and organize our entire program. The first of these is inheritance.
As someone with some prior C# knowledge before this tutorial, this has been extremely useful, thank you very much for putting so much detail into each tutorial.
One question I have about structs is precisely why ever use them over classes? I understand they run faster, but I can't really see the point of even keeping structs in your mind at all when classes are seemingly used so much more? I'm not sure if you'll even read this, but if you do, some insight on this would be greatly appreciated.
That's a good question, and it has a complicated answer. I think 99% of the time, I make a class and not a struct.
One big disadvantage of classes is that they require dynamic memory allocation. When you say new Point(), memory has to be allocated that has to be cleaned up later on. That's different than if you defined Point as a struct. If you have a local variable whose type is Point, the only memory you need to make is attached to the method itself (on the stack, if that means anything to you) and when the method ends, it is cleaned up automatically and cheaply.
Similarly, if you have a Point[] with, say, 50 items in it, if Point is a class, you'll end up with 51 total objects being created: one for the array itself, and 50 for each individual point. In contrast, if {{Point} is a struct, you end up with a single block of memory for the whole pile (big enough to hold all 50 points and any array metadata).
It's just a different memory usage model. It has its places—and actually more often in games than in other types of applications. XNA's and MonoGame's Vector2 and Vector3 types are structs, for example.
But in the short term, it is not unreasonable to just stick with classes for simplicity.
Post preview:
Close preview