Exploring Record Types in C#
Detailed explanation of record types, introduced in C# 9 (.NET 5), and their use cases.
Background
Before we understand record in C#, let’s quickly understand what are reference and value types. In C#, any variable declared can be either reference or value types.
Reference type
Classes, Interfaces, Delegates, Arrays are all the example of reference types. Let’s say we have a class as below:
public class Student {
public string Name {get;set;}
}
Student s1 = new Student {Name = "Steve"};
Student s2 = new Student {Name = "Steve"};
When compiler encounters declaration of variable s1, it will store data in memory and will assign it’s address to s1. Basically, s1 will store the address of where the student details are in the memory. Same with s2. So, in our example s1 and s2 stores different addresses as they were declare independently, and as a result if we compare them, the result will be false.
Console.WriteLine(s1 == s2); // This will be false
However, if we execute as below:
Student s3 = s1;
Console.WriteLine(s3 == s1); // This will be true
This is because, we are assigning address stored in s1 to s3 and as a result both will point to same address.
Value type
Int, float, bool, structs etc. are example of value types. Let’s see example below:
int a = 4;
int b = 4;
Console.WriteLine(a == b); // This will be true
This is because, when compiler encounters declaration of variable of type int a, it stores the actual value in a (i.e. 4 in our case) and not it’s address to the memory allocation.
What is Record
Record types enables us to create class type variable, but instead of reference type, they will be of value type. Record can be either of type Class or Struct. Let’s see an example:
using System;
public record Student(string Name);
public class C {
public static void Main() {
Student s1 = new Student("Steve");
Student s2 = new Student("Steve");
Console.WriteLine(s1 == s2); //this will be true
}
}
//The statement will be false if we replace record with class in above example
Record also instructs compiler to generate members such that they are more useful to store data. Records are useful when we want to create objects which can be compare as value types and we don’t want to allow to change values of it’s member.
s1.Name = "John" // This will throw a compiler error, as we can't change values
Achieving Immutability
If we use primary constructor and doesn’t use auto property syntax then by default, record types makes properties as read-only. Here is an example you can try in ShartLab.io:
using System;
public record Student2(string Name); //Primary constructor
public class C {
public static void Main() {
Student2 s3 = new Student2("s");
s3.Name = "s"; // Compiler error will be thrown
}
}
Using With
Let’s say we would like to create a copy of one record from another one. Here is how the code will look like:
Student s3 = s1;
//s3.Name will be "Steve" and once assign, we cannot change that.
However, while copying, we just want to change only one of the property. To do that, we can use with. Here is an example:
public record Student{
public string Name {get;set;}
public int Id {get;set;}
}
Student s1 = new Student(){Name = "Steve", Id = 1};
Student s2 = s1 with { Id = "2" };
//s2.Name will be Steve, but s2.Id will be 2.
As a result, s2 and s1 will not be same.
Struct vs Class Records
By default declaration will create record of type class. Meaning public record RecordName() will create class type record. However, we can create struct type by declaring it public record struct RecordName(). The main difference between class type and struct type are:
- Storage: Class stores data in heap vs struct stores data in stack. Remember that, heap is expensive to maintain compare to stack. So, the obvious gain is in performance.
- Data structure: Use struct if you have very simple data structure to deal with vs class where you have a very complex structure.
Conclusion
Record type is best when we need immutability and value comparison. It’s best fit is when we have a data centric application with fixed values. They are also best for pattern matching scenarios. Further, we also want comparison based on values and not references.