In this article, we’ll discuss the differences between record struct and record class in C#. First, we will shortly explain each of the types. Afterward, we’ll show and describe each difference between them.
Let’s start.
Record Struct and Record Class in C#
C# is built upon two categories of types: value types and reference types. Each manages memory differently, and their equality is different.
A class
is the reference type. That implies that when we pass an object as an argument to a method, we pass the reference to that object. If we create two objects of the same class with the same properties’ values, these objects won’t be the same. Struct
is, on the other hand, value type. When we pass a struct as an argument to a method, we pass a copy of this struct. Analogously, two value types are equal when their values are the same.
Since C# 9, we have a new record
type. A record
type is the reference type. But, unlike other reference types, two records are equal if they are the same class and if the value of every field is equal. We use record
type when we want an immutable reference type with value-based equality. Records are ideal for working with data structures.
record
type, read our article Records in C#.From C# 10, we also have a record struct
type. It is the value type, but it adds some features record
added to the class
types.
Differences Between Record Struct and Record Class in C#
Let’s first define two similar types. First, the record class
type:
public record class PersonRecordClass(string Name, DateTime BirthdayDate, int YearsOld, string[] PhoneNumbers);
We define a record class
type named PersonRecordClass
. It implements the primary constructor, and the compiler generates public properties for the constructor’s parameters. The primary constructor parameters are also called positional parameters. We could omit the class
keyword and use only the record
keyword.
After that, we define a similar record struct
type PersonRecordStruct
:
public record struct PersonRecordStruct(string Name, DateTime BirthdayDate, int YearsOld, string[] PhoneNumbers);
Now we’re ready to discuss some of the differences.
Allocation/Deallocation
A record class
is a reference type. It is allocated on the heap, and the garbage collector will deallocate it when there is no reference to it anymore.
A record struct
is a value type. It is allocated on the stack or inline with the containing type. Consequently, it will be deallocated when the stack unwinds or when the containing type is deallocated.
Default Value
Let’s examine the default values of the defined record class
and the record struct
:
public static PersonRecordClass? GetRecordClassDefaultValue() { return default(PersonRecordClass); } public static PersonRecordStruct? GetRecordStructDefaultValue() { return default(PersonRecordStruct); }
We can print the return values of these methods:
Record class default value is null Record struct default value is PersonRecordStruct { Name = , BirthdayDate = 01/01/0001 00:00:00, YearsOld = 0, PhoneNumbers = }
The default value of the record class
is null, as is the case for any other reference type. The default value of the record struct
is the instantiated object with the default values for each property. The Name
property is a string
, and PhoneNumbers
is an array
. Thus, the default values of both reference types are null
.
Inheritance
A record class
can inherit from another record. It can’t be inherited from a class:
public record class PersonWithAddressRecordClass(string Name, DateTime BirthdayDate, int YearsOld, string[] PhoneNumbers, string Address) : PersonRecordClass(Name, BirthdayDate, YearsOld, PhoneNumbers);
The record class
PersonWithAddressRecordClass
inherits from the previously defined PersonRecordClass
. The derived class doesn’t hide the base class’s properties and only generates the properties for new parameters in the constructor. In our example, that would be a string Address
.
A record struct
can’t inherit other classes.
Immutability
If we declare a record class
with a primary constructor, it is immediately immutable. We won’t be able to change any of its properties after construction. Another way to achieve a record class
immutability is by using the init-only setters:
public record class PersonRecordClassInitSetters { public required string Name { get; init; } public DateTime BirthdayDate { get; init; } public int YearsOld { get; init; } public required string[] PhoneNumbers { get; init; } };
The PersonRecordClassInitSetters
record class has the same property as the PersonRecordClass
. The difference is that instead of using a primary constructor, we defined these properties as init-only, and we won’t be able to set them after the record construction.
On the other hand, a record struct
is not immutable out of the box. It even won’t be immutable when defined with positional parameters. But there is an easy way to make it. We only have to add readonly
keyword in the record struct definition:
public readonly record struct PersonRecordStructReadOnly(string Name, DateTime BirthdayDate, int YearsOld, string[] PhoneNumbers);
Let’s finish up with a brief overview of the differences.
Overview of Differences Between Record Struct and Record Class in C#
record class
and record struct
are pretty similar, but they also provide some crucial differences.
Let’s have a short look at them once again:
Record class | Record struct | |
---|---|---|
Allocation | heap | stack |
Deallocation | garbage collection | stack unwind |
Default value | null | struct with default field values |
Inheritance | yes | no |
Immutability | no with primary constructor or init-only setters | no with readonly keyword |
Conclusion
.NET introduces the record type to provide value equality to reference types. Records are primarily used for lightweight, immutable data structures.
After introducing the record class, .NET also introduces record struct and adds some valuable features similar to the record class.
Generally, we’ll choose the record struct to pass values instead of references. On the other hand, the record class is ideal for defining the Data Transfer Objects (DTOs), as we want to have only one instance of this data structure in almost every case. We will also choose the record class when our types have to support inheritance.