Is it a record or a Record

When records were introduced in C# 9, it reminded me of Pascal's records. Pascal was the first language I programmed in. Despite the existence of VB, C++, Java and even C#, my teachers felt Pascal was just the perfect language to teach us programming. Reading about C# records years later took me down memory lane and started wondering whether there are similarities with Pascal records. I know what you are thinking - with all the advances in computing power and modern compilers how can there be similarities? Nevertheless, I was on a journey to satisfy my curiosity.

Let's briefly look at records in Pascal. Just like in C#, a record in Pascal was a way to create complex structured data types. Let's create a Person record:

type Person = record firstName : string; lastName : string; city : string; end;

var person1: Person;

We can then use the created instance to assign and read individual properties using dot notation similar to modern languages.

person1.firstName = 'john';
person1.lastName = 'doe';

writeln('First name is: ', person1.firstName)

You can also access properties using with keyword. You can compare Pascal records using @ operator (@person1 = @person2). This is equivalent to ReferenceEquals in C#. You can also override comparison operators to customise the equality check of objects. Pascal records are limited in that they do not support encapsulation and do not enforce immutability.

C# records, what are they and for what

A record is a reference type, similar to classes, that provides value-based equality out of the box. C# 10 goes further to add record structs - value type records. You can achieve value-based equality using classes as well but you will not get any help from the compiler. Let's look at some examples to make sense of this:

//define a record with 2 properties
public record class PersonRecord (string FirstName, string LastName);

//create two instances of person record
PersonRecord personRecord1 = new PersonRecord("John", "Doe");
PersonRecord personRecord = new PersonRecord("John", "Doe");

Console.WriteLine($"{personRecord == personRecord1}");
Console.WriteLine($"{personRecord.Equals(personRecord1)}");

//Outputs
True
True

First, note the definition of the record class - there is no explicit definition of FirstName and LastName properties. However, you can still refer to them just as you would for class-defined properties i.e. personRecord1.FirstName. Second, you might be wondering how come both statements for comparing the two instances output true. Let's look at how you define a similar normal class:

public class PersonClass
{
    public string FirstName { get; }
    public string LastName { get; }
    public PersonClass(string firstName,string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}


PersonClass personClass = new PersonClass("John", "Doe");
PersonClass personClass1 = new PersonClass("John", "Doe");
Console.WriteLine($"{personClass == personClass1}");
Console.WriteLine($"{personClass.Equals(personClass1)}");

//Outputs 
False
False

Notice how much code you have to write to define a normal class of the 'equivalent' record class. Even after writing so much more code, you get different results when comparing two instances of the class. To get the same results when comparing two different instances of the same class as that of record instances, you need to implement IEquatable<T> interface as well as override ==, != operators and GetHashCode.

What is happening under the hood is that in addition to generating public properties on the record type based on the constructor parameters, the compiler also generates an implementation of IEquatable<T> interface and equality operator overrides.

Records provide many additional features. Among them is non-destructive properties defined through constructor positional parameters as in the example record definition above. A ToString() override also provides formatted output for record instances. Record types can only inherit from other record types. They cannot inherit class or struct types and vice versa.

In addition to a record class, you also have a record struct. What is the difference? A record struct is a value type as opposed to a reference type of record class. Remember that for reference types, you only pass a reference to your object and not the actual object. For a value type, you pass the actual object. The decision of whether to use a record class or record struct is largely dependent on how large your object can be. If it is small enough not to be an overhead copying it around and you want to utilise the features of a record, then a record struct will make sense otherwise a record class is ideal.

The record type is one piece of evidence of what Microsoft's C# and Visual Studio teams have been paying attention to in the last few years: developer productivity. This is a topic for another day though but it's worth just mentioning here. In all, there is little similarities between C# and Pascal records!