Home C# 10 - Record Class vs Record Struct
Post
Cancel

C# 10 - Record Class vs Record Struct

Before we start, it is essential to know that .NET categorizes objects as value types and reference types. The memory is managed in a way that value-type instances are stored statically with their values on the stack memory, while reference-type variables are stored dynamically on the heap memory, holding references to their data. This also means that their default equality differs in the sense it’s based on values for value-types and on pointers for reference types.


Why Record class?


Comparing value type variables of the same type and with the same values will result in equality. On the other hand, the same thing with reference type variables will result in a difference, unless you deal with IEquatable and GetHashCode overrides to make it happen.

C# 9 introduced a new reference type named record, and it was meant to get rid of this costly boilerplate, stating a convenient implementation for building immutable data structures with:

  • An override of ToString().
  • An override of Object.Equals(Object).
  • An override of Object.GetHashCode().
  • A virtual Equals method whose parameter is the record type.
  • Methods for operator == and operator !=.
  • Implement System.IEquatable.

C# 10 introduced an optional keyword for evidence when a record is a class, as you will see further down.

Are Records immutable on their own?

No, but there are two ways to ensure it. The first is by using init-only setters like this:

// The class keyword is optional for evidence it's a reference type
public record class Planet() {
  public int ID { get; init; }
  public string Name { get; init; }
  public IEnumerable<string>? Moons { get; init; }
}

The second way is by making a positional record and passing an ordered list of arguments in the constructor, it will make them immutable by default:

public record class Planet(int ID, string Name, IEnumerable<string>? Moons = null);

var earth = new Planet(1, "Earth");

Copy that

Once they are immutable, if defined as above, you can no longer change their values but only create a copy with new ones instead. By using with expressions, you can set new values between the braces with properties like this:

var earth = new Planet(1, "Earth");
var anotherPlanet = earth with { "Saturn" }; // new Name, although Id was copied the same

Or simply let it empty to have a full-copy:

var anotherPlanet = planet with { }; // every property was copied


Structs


Struct is a very known Value Type in the .NET world. One can say they’re a light version of classes due to the way they structure data. Because they’re value types, it makes them cheaper in memory allocation, resulting in better performance.

However, there are other differences, like not allowing inheritance at all. Also, when it comes to equality comparison, there are limitations, as you can see given these two structs:

var firstProbe = new Probe() { PlanetID = 1, Name = "Probe 1", new DateOnly(1957, 10, 04) };
var secondProbe = new Probe() { PlanetID = 1, Name = "Probe 1", new DateOnly(1957, 10, 04) };
var areEqual = firstProbe == secondProbe;
  • Even the variables being of the same type and having the same values, the == operator isn’t available for structs, resulting in false. It could be only performed by using .Equals() method.
  • Printing it would output its type name, Models.Probe.

A brand new type: Record Struct

public record struct Probe(int planetID, string Name, DateOnly flybyDate);

Finally, C# 10 introduces a new value type, the record struct. Simply put, it brings many capabilities records added from classes to structs:

  • Use with expressions.
  • Creation with positional parameters for immutability.
  • Equality comparison with == and != operators.
  • PrintMembers and ToString() methods.

Are Record Structs always immutable?

Differently from the record class type, making it a positional record won’t make the record struct immutable on its own. However, directly changing its value is still be possible:

var russianProbe = new Probe() { PlanetID = 1, Name = "Sputnik 1", new DateOnly(1957, 10, 04) };
var americanProbe = russianProbe with { Name = "Explorer 1" }; // a copy with a change
americanProbe.FlybyDate = new DateOnly(1958, 02, 01); // but it is still possible to change the value directly

This problem can be easily solved by using the readonly keyword:

public readonly record struct Probe(int planetID, string Name, DateOnly flybyDate);

And your record struct becomes immutable.


Hands-On


I have written a simple example using both record class and record struct to use most of the theory described above.


Final thoughts


Part of our job as developers is making the right decisions, including using the proper types for our data structures. C# is in constant evolution to help ease such choices. Consider aspects like code reusability, memory allocation cost, and performance when choosing types.


Check the project on GitHub



This post is licensed under CC BY 4.0 by the author.