Class versus Struct Explained — C#

Alexandre Malavasi
7 min readMay 25, 2021

--

The C# language is an extensive thing to learn and any dedicated developer may spend several years studying and learning the language and it would not be enough to know absolutely everything in C#. There are some basic concepts that could be missed even by senior developers, which is normal considering many concepts and features are being daily used but without notice that they are there.

The use of structs is one of those examples, there are tons of implementations and sample among native .NET libraries, but it is not common to see they being created by developers. I judge that there are two main reasons for that:

  1. There is a consense in a project that structs are suitable for the specific scenario.
  2. Missing knowledge on how structs work and when they should be accordingly used.

Before we start walking through the theory behind the difference between classes and structs, it is important to take a look at the syntax of each of them.

The following examples represent a class and a struct for handling Passport information, including store and validate the underlying data.

Passport class

Passport class

The passport class contains three fields: number, expiration, and country. It was kept simple for demonstration purposes. In a real scenario, the structure would be different. The implementation of the validation method is missing as well as it is not relevant for this article.

Passport struct

On the other hand, a struct version of the Passport class would be like the image below:

At the first moment, the only difference between the class and struct was the related keywords “class” and “struct” in terms of syntax. However, what occurs behind the scenes in runtime is completely different and might affect the behaviour of the system significantly.

To start with the characteristics of each one, I’d like to emphasize what happens when we copy a struct and when we copy an instance of any class.

In lines 9 and 10, an object of the Passport class is created with the number “123” and just after that a second Passport is created, receiving the first passport as an assignment. And finally, in line 14, the number of the second passport is changed to “456”. If we print in console the values for both of the passports, we get the following result:

As you can see, the number of the first passport changed as well, despite the real intention was to change only the second passport. Why did that happen? Because classes in C# are Reference Types, which means that the object is stored in one place of the memory and the underlying value for the same object is stored in another place, being liked by reference. In other words, once an object is assigned to another object, like a copy, both of the objects have references to the same place in the memory where the actual data is stored, as represented in the following image:

The stack memory is a privileged part of the memory that has high performance and is frequently used by programs to small information. Access to this part of the memory is faster than Heap memory. In C# language, any class is considered as Reference Type, which means that only a reference to the object will be stored in Stack and the actual value will be stored in Heap.

In opposite to Reference Types, there are the Value Types in the C# language, which will have the “object”/variable stored on Stack, but their actual value as well. Giving an example using the Passport struct, the code would have the following representation:

Despite the syntax is similar to the previous example, the result in terms of memory usage is completely different. First of all, after changing the number of passport two, passport one is not affected, as it follows:

The reason for that is that the value is being stored in the heap memory as well. So, a copy of the object not only copies its reference, but the underlying value as well, as demonstrated in the image below:

Any change on passport one will never affect passport two as they represent different locations in the memory. But, the difference between classes and struct does not end here.

As structs are managed in Stack memory, they are not the responsibility of the Garbage Collector, being removed from the memory immediately when they are not being used in the scope of the program.

Integer, String, and any primitive types are considered as reference types in C#, and in the next example, specific variables (int and string) are created inside an if statement:

Considering string is a reference type, the variable “message” will be “destroyed” from the memory just after the if statement runs, considering the variable is not used outside the scope of the if statement. And speaking of structs, some of the primitive types, like integer, are actually structs:

Therefore, structs are largely used in native implementation among .NET libraries, and know about them and how they work is important to consciously write our code.

Considering structs do not depend on the Garbage Collector to be destroyed as they are eliminated once it is not in the scope, it does not make sense to have a “destructor” method in structs as we have for classes. if you try to specify a destructor in a struct, the compile will show the following error:

Additionally, you cannot specify an empty constructor for a struct. In that case, any constructor needs to populate all the properties in the struct, as seen in the following image:

if we create a constructor in a struct and do not assign all the properties on it, the compile complains as it violates the purpose of the struct:

In that case, I intentionally assigned only the Number property and the compile error says that I have to fully assign the values for Expiration and Country properties as well. It is related to the recommended immutability of the struct in the memory in order to gain performance.

Conclusion

Structs and classes are the subjects of debate in the market regarding when each one must be used for. In my personal opinion, it makes sense to use structs in the following scenarios combined:

  • when the data is simple
  • can have all the values assigned on its creation
  • when the performance is critical for the system
  • the objects are short-lived

The main objective of this article is to simplify a complex subject, therefore there are many other aspects that are not covered in this post. So, I recommend you to take a look at the official documentation below:

Thank you for reading this article. Feel free to connect with me in my social media profiles:

Twitter: https://twitter.com/alemalavasi
Linkedin: https://www.linkedin.com/in/alexandremalavasi/
Youtube: https://www.youtube.com/channel/UC-KFGgYiot1eA8QFqIgLmqA
Facebook: https://www.facebook.com/alexandre.malavasi.dev

My Book

I’m glad to announce that I have my first book published. It is a deep dive hands-on through the most common Design Patterns used in .NET applications. The book contains hundreds of code samples and explanations based on real-world scenarios. It also has many examples on Object-Oriented Programming, SOLID principles and all the path to get yourself familiar with .NET 5 and C#. Check it out:

--

--

Alexandre Malavasi

Microsoft MVP | MCP | MCTS | MCPD | ITIL | .NET | MBA | MTAC | Technical Leader | Consultant | .NET Developer