Skip to content
Theory · Enums, records and patterns · lesson 3 of 8

Records

An enum holds one value from a list. Often you need something else: to keep several related pieces of data together and compare them as a whole.

Think of an office pass: it carries a name and a room number. The guard at the door compares the data, not the card itself. If you lose your pass and get a new one printed with the same data, for the guard it is the same pass.

An object of a class is compared differently. In the theme on value and reference types, == on two class objects with equal fields gave false. It checks whether it is one card, not whether the data on the cards is the same.

For data that is compared by its contents, C# has records—types declared with the word record:

C#
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a == b); // True
Console.WriteLine(a);      // Point { X = 1, Y = 2 }

record Point(int X, int Y);

Let's take the program apart line by line:

  1. record Point(int X, int Y); declares the record Point with two properties: X and Y. The whole declaration fits on one line.
  2. new Point(1, 2) creates a record the same way new creates a class object: X gets 1, Y gets 2.
  3. a == b gives true: a and b are two different records, but their data is the same.
  4. Console.WriteLine(a) prints the type's name and all the data: Point { X = 1, Y = 2 }.

What one record line gives you

The record Point is called positional: its data is listed in parentheses in order, and you pass it to new in the same order. From this line, the compiler writes for you what a class would need written by hand:

  • the properties X and Y: you can read them through a.X, but not change them after creation;
  • a constructor with the parameters X and Y, so both new Point(1, 2) and a call with named arguments, new Point(X: 1, Y: 2), work;
  • comparison by data: ==, != and Equals compare the properties one by one;
  • a ToString method that returns the string Point { X = 1, Y = 2 } for printing.

A record and a class

Compare the record with a class that holds the same data:

C#
var c1 = new Spot(1, 2);
var c2 = new Spot(1, 2);
Console.WriteLine(c1 == c2); // False
Console.WriteLine(c1);       // Spot

class Spot
{
    public int X;
    public int Y;
    public Spot(int x, int y) { X = x; Y = y; }
}

c1 and c2 hold the same data, but == compares references, and they lead to two separate objects. Printing shows only the class name: a class's ToString doesn't know by default what is in its fields.

Interviewers often ask how a record differs from a class. The short answer:

  • a record is compared by its data, a class object by its reference;
  • a positional record gets its properties, constructor and ToString for free, and that ToString returns a string with all its data;
  • the properties of a positional record cannot be changed after creation.

This is where the pass analogy breaks down. A real pass cannot sit in two pockets at once. But a record is a reference type, like a class: after var b = a;, two names lead to the same record. For Point that is safe: the properties of a positional record cannot be changed, so nobody can replace X or Y of a through b.

Common mistake

Someone tried to change a property of a record:

C#
var a = new Point(1, 2);
a.X = 5; // ✗

record Point(int X, int Y);
TEXT
error CS8852: Init-only property or indexer 'Point.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

The program doesn't compile. The properties of a positional record are init properties: they get their values only at creation. To get a point with X = 5, create a new record: a = new Point(5, a.Y);.

Key points

  • record Point(int X, int Y); declares a record with the properties X and Y, a constructor and a ToString that gives Point { X = 1, Y = 2 }.
  • Records are compared by data: two records with equal properties are equal, and == gives true.
  • On class objects, == compares references, so two objects with equal fields are not equal.
  • The properties of a positional record cannot be changed after creation: a.X = 5; doesn't compile.