# Records vs Classes in C#
> Why do record types in C# exist when classes work fine? Jamie attempts to explain where you could use each.

*24 July 2022* · Jamie Taylor


The cover image for this post is by [Eric Krull](https://unsplash.com/photos/fi3_lDi3qPE)


---

This blog post was written by [Jamie](https://linkedin.com/in/jamie-taylor-rjj-software).


## Introduction


Version 9 of the C# language brought a large number of things with it

[here's a list of the things](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9) that C# 9 brought to the table


Among those changes were things like Top-level Statements and Code Generators, but my favourite is the `record` type.

But what is a `record` and why _might_ you use one rather than a `class`?

## Records and Classes


A lot of developers where taught [OOP](https://en.wikipedia.org/wiki/Object-oriented_programming) principles, and these have served us well. Encoding the properties which describe a thing and it's thing-ness is a great way to design the solution to _some_ problems. Look at some physical object around you, they all have properties which can likely be described by types in your programming language of choice (and by adding a few custom types, too). You're probably reading this blog post on a phone, tablet, or computer of some kind, so let's take a whack at describing it using OOP:

```csharp
public enum Currency
{
    GBP = 1,
    USD = 2,
    EUR = 3
}
public enum DeviceType
{
    Desktop = 1,
    Laptop = 2,
    Tablet = 3,
    Phone = 4
}

// Represents devices which can be used to read stuff
public interface ICanRead
{
}

public class Device : ICanRead
{
    public string Name { get; set; }
    public decimal OriginalCost { get; set; }
    public Currency PurchaseCurrency { get; set; }
    public string SerialNumber { get; set; }
    public DeviceType DeviceType { get; set; }
    public Double ScreenSize { get; set; } 
    // etc.
}
```

This section of C# code is a nice way to start describing the properties (or things) that make up a device and contribute to it's thing-ness. This is a fantastic use of a class, especially the inclusion of inheritance; however, this has lead to an [anemic domain model](https://en.wikipedia.org/wiki/Anemic_domain_model) - the `Device` class is a domain model, as we're describing something within the domain of "devices that I can use to read stuff with", but it contains no business logic.

This isn't necessarily a bad thing, but there's a way to describe you domain objects without falling into the anemic domain model.

## Records to the rescue?


In a blog post by Mads Torgersen, he describes the `record` type as:

> A record is still a class, but the record keyword imbues it with several additional value-like behaviors. Generally speaking, records are defined by their contents, not their identity. In this regard, records are much closer to structs, but records are still reference types.

While records can be mutable, they are primarily built for better supporting immutable data models.
>
> — Mads Torgersen, [C# 9.0 on the record](https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/)


Let's say that you are constructing an object which you know that you'll never mutate: a view model, for instance. You _could_ use a `record` type. Here's the `Device` code from earlier, but as a `record`:

```csharp
public enum Currency
{
    GBP = 1,
    USD = 2,
    EUR = 3
}
public enum DeviceType
{
    Desktop = 1,
    Laptop = 2,
    Tablet = 3,
    Phone = 4
}
public record DeviceRecord(string Name, decimal OriginalCost,
    Currency PurchaseCurrency, string SerialNumber,
    DeviceType DeviceType, Double ScreenSize);

DeviceRecord someNewDevice = new("Jamie's Laptop", 1499.0m,
    Currency.GBP, "A1B2C3D4E5",
    DeviceType.Laptop, 15.0);
```

One caveat here is that you can't use [Object Initializers](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#object-initializers) to set up `record` instances that you've declared in this way. For instance, you _can't_ do:

```csharp
var someNewDevice = new DeviceRecord
{
    Name = "Jamie's Laptop",
    OriginalCost = 1499.0m,
    PurchaseCurrency = Currency.GBP,
    SerialNumber = "A1B2C3D4E5",
    DeviceType = DeviceType.Laptop,
    ScreenSize = 15.0
};
```

You'll get an error message similar to the following if you do try:

> There is no argument given that corresponds to the required formal parameter '`Name`'  of '`DeviceRecord.DeviceRecord(string, decimal, Program.Currency, string, Program.DeviceType, double)`'
>
> — Roslyn


This error message is telling us that even though we can do `someNewDevice.Name` to get the property, we cannot refer to it in an object initialiser.

But one thing that you _can_ do is use `with` to copy over the values of properties like this:

```csharp
// Records support inheritance, too
public record DeviceRecordRedux : ICanRead
{
    public string Name { get; init; }
    public decimal OriginalCost { get; init; }
}

var device = new DeviceRecordRedux { Name = "Jamie's Laptop", OriginalCost = 1499.0m };
var otherDevice = device with { Name = "Spare device" };
```

We have to change the way that we declare the `record`, because `with` uses Object Initialisation. But we also get copy construction for free if we do this:

> The with-expression works by actually copying the full state of the old object into a new one, then mutating it according to the object initializer. This means that properties must have an init or set accessor to be changed in a with-expression
>
> — Mads Torgersen, [C# 9.0 on the record](https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/)


## Equality: A Warning


Records perform value-based equality, similar to `struct` types. This means that it's possible to be caught out by some equality gotchas:

```csharp
var device = new DeviceRecordRedux { Name = "Jamie's Laptop", OriginalCost = 1499.0m };
var otherDevice = device with { Name = "Spare device" };

// The following will both return false, as we'd expect
Console.WriteLine(ReferenceEquals(device, otherDevice));
Console.WriteLine(Equals(device, otherDevice));

var original = otherDevice with {Name = "Jamie's Laptop" };

// This returns false
Console.WriteLine(ReferenceEquals(device, original));
// But this returns true. Why?
Console.WriteLine(Equals(device, original));
```

The call to `Equals(device, original)` returns true because both `device` and `original` have the same **values** for their properties (using value-based equality), whereas `ReferenceEquals(device, original)` returns false because they don't refer to the same **instance** of `DeviceRecordRedux`.

## Why Use Records?


Well, they support immutability (although are mutable themselves), have support for the `with` keyword (for copying all values of another `record` instance), and you also get formatting for displaying your record:

```csharp
var deviceAsClass = new Device
{
    Name = "Jamie's Laptop",
    OriginalCost = 1499.0m
};

var deviceAsRecord = new DeviceRecordRedux {
    Name = "Jamie's Laptop",
    OriginalCost = 1499.0m
};

// Shows "Program+Device"
// i.e the type-name
Console.WriteLine(deviceAsClass);

// Shows "DeviceRecordRedux { Name = Jamie's Laptop, OriginalCost = 1499.0 }"
// i.e. useful data
Console.WriteLine(deviceAsRecord);
```

I also think that they are easier to reason about and deal with when you need a class which will either be anemic or will be immutable. ViewModels and DTOs are fantastic for this - any type of object which will only contain data, but not have methods hanging off of it or expect that data to change much.

The `record` type also leans very heavily into the functional programming paradigm, too. And with C# being multi-paradigm (it supports both OOP and functional programming), this is a great addition to your language toolkit, even if you never do functional programming.

