Classes and structs have been a part of C# since version 1.0, but more recently records have been added. In this article, we will compare these three structures to see how they each have a use-case where they are the best data structure to use.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in and see when to use records, classes, and structs in C#.

Classes in C#

Classes are reference-type data structures in C#. They are the most versatile data structure available, but they also come with more overhead in performance than structs or records. This is not to say that classes are a poor choice of data structure to use. Highly optimized, classes offer a wide range of features, making them a powerful tool for writing code.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

That said, let’s inspect a typical definition of a class:

public class ApiClient : HttpClient
{
    private string _myField = "";

    public string MyProperty { get; set; }

    public ApiClient() 
    { 
        BaseAddress = new Uri("http://somefakeapi.com"); 
    }

    public string MyMethod()
    {
        return "hello";
    }
}

Let’s review a few features of C# classes that we will use as comparison points against the other data structures in C#.

Classes can inherit behavior from other classes. Being reference types, when instantiating a class, the memory allocation will occur on the heap rather than the stack. This typically means there’s more overhead in using reference types as compared to value types. Classes are mutable meaning they can change after instantiation.

Structs in C#

Structs are lightweight value-type data structures in C#. They are best used to represent simple data with little or no behavior.

Let’s look at a typical struct implementation of a GeoLocation struct:

public struct GeoLocation
{
    private string _latitude;
    private string _longitude;

    public Geolocation(string latitude, string longitude)
    {
        _latitude = latitude;
        _longitude = longitude;
    }

    public override string ToString() => $"(lat: {_latitude}, lon:{_longitude})";
}

Structs should be treated as immutable meaning they should not change after instantiation. This is due to their copy semantics and it is generally good coding advice. Creating a readonlystruct is how we can truly create an immutable struct. Since value types allocate on the stack, array allocation/deallocation is generally much cheaper than when using reference types. Lastly, structs cannot inherit from another. Typically, if we want this behavior we should use a class.

How Classes and Structs Differ

Keeping in mind the properties of each, class and struct, we can conclude that there are situations where each would be the optimal data structure to use.

If we want to encapsulate a complex data structure with robust behavior we should look to use a class. Additionally, the larger an instance of an object the more performant the use of class is. A good example of this is copy semantics. During a copy operation reference types copy a reference to the instance on the heap. On the other hand, when copying a value type a new space in memory is allocated every time.

We want to utilize structs when we want to encapsulate a simple data structure. small value-type objects like structs are the better data structure to create collections with. This is because they are more performant to allocate when compared to a class data structure. 

Records in C#

Records are lightweight reference types with value-based semantics. Additionally, the compiler offers us some features like value equality, construction, destruction, and display formatting. In many ways, they are somewhere between class and struct.

Let’s look at two ways to define a record in C#:

// create record, no positional parameter
public record ApiData
{
    public int Id { get; init; }
}

// create record with positional parameters
public record OtherApiData(string Id);

Like classes, records are reference types by default. We can define constructors and properties in a record. Records can make use of inheritance. Similar to structs, we should use records as small immutable data structures. Yet, records are flexible enough to allow mutability.

C# has, more recently, introduced record class and record struct.

record class is synonymous to record but it explicitly describes it as a reference type. A record can inherit from other records. On the other hand, a record cannot inherit from a class or vice versa.

record struct is a value-type record. This allows it to gain the performance advantages of a value type while retaining features like equality comparison, and display formatting. record structs cannot inherit from other record structs.

When to Use Struct

Choosing when to use struct is simple and can be broken down into four criteria:

  • It logically encapsulates a single value (ex. Coordinate Point)
  • It has a total instance size of under 16 bytes
  • The data is immutable
  • It will not be boxed frequently

If all of these are true then struct or record struct should be used. If not then we should use a reference type like class or record. To be clear, it is only beneficial to choose structs over classes when all the guidelines are met.

When to Use Record vs Class

Our motivation for making the data structure will determine whether we use a record or class.

If a structure will perform complicated behaviors and will be a large instance these are signs we should use a class. Additionally, we should consider whether we will need this structure to change after creation. If so, then class is the better choice.

We should use record when we need to encapsulate data without complicated behavior (a common example of this scenario is Data Transfer Objects – DTOs). This would be a more fitting situation for records especially due to their built-in optimizations. Records are not more performant in other regards such as equality comparison.

Conclusion

In conclusion, classes, records, and structs all have use cases in C#.

Classes are great when we want to represent complex behavior. We use records when we want to represent a simple data set that should be immutable. Finally, we use structs if it fits into all four criteria discussed.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!