For the record!

One of my favorite features of the C# language may have been released with 9.0. I’m talking about records. What is a record you ask? Records are a new type introduced in C# 9.0. You can think of it as the in between of a struct and a class. A record is a reference type, meaning we’re going to point to a memory location, with value based evaluation. Value based evaluation is when you compare two records you are actually comparing their values and not their referenced location. We will look at that here shortly.

Another important part of records is that they use the new init keyword for their setters by default. Using records we get immutable types out of the box. With that, see what I did there, we get a new with keyword that will help us handle creating new records. So a javascript developer will finally start to feel at home. Here are some some examples.

 

New Record

Records are going to look like a lazy person’s version of a class. There are two different ways for you to construct your records. You can either have public properties that get set at initialization, or just a constructor with variables that will represent public properties. Look at the two of these. I’m using snips for a top level program that can be found here. This feature was also released with .Net 5.0 and C# 9.

First we have the properties defined.

static void PrintCar()
{
    var myCar = new Car() 
    {
        Model = "Mustang",
        Owner = new Owner()
        {
            Name = "Bryan"
        }
    };

    WriteLine(myCar);
}

record Car : Vehicle
{
    public Owner Owner { get; init; }
}

record Owner
{
    public string Name { get; init; }
}

record Vehicle
{
    public string Model { get; init; }
}

This image show the print out from the above code. Notice we actually get the serialized version of the Car.

 

So here we have Car, Owner, and Vehicle records. Notice all are records. A record can inherit from another record. You can see that relationship between Car and Vehicle. We can’t mix our classes and records. So once you commit to one, you are stuck with it. We can also represent this using my preferred method, constructor initialization.

Now we have constructors defined.

static void PrintCar()
{
    var myCar = new Car("Mustang", new Owner("Bryan"));

    WriteLine(myCar)
}

record Car(string Model, Owner Owner) : Vehicle(Model);

record Owner(string Name);

record Vehicle(string Model);

 

With and Equals

Here you can see that we don’t have to explicitly define our properties. The compiler is going to do that work for us. When it does, it will automatically use the init setter. Now we can look at with and equality.

So we will reuse our constructor initialized records, this will also allow us to deconstruct our objects too. I will show you what that looks like.

static void PrintIsEqual()
{
    var myMustang = new Car("Mustang", new Owner("Bryan"));

    var myCamaro = myMustang with // our with keyword 
    {
        Model = "Camaro"
    };

    WriteLine(Equals(myMustang, myCamaro)); // this will print false

    var myNewMustang = myCamaro with // our with keyword 
    {
        Model = "Mustang"
    };

    WriteLine(Equals(myNewMustang, myCamaro)); // this will print false

    WriteLine(Equals(myNewMustang, myMustang)); // this will print true

    var (model, owner) = myNewMustang; // deconstruct our mustang into two separate variables

    WriteLine(owner); // this will print the owner's properties 
    WriteLine(model); // this will print the model
}

Here is what the console looks like when we run the above code.

If you run this example you can see how the equality evaluates. When we use with out object’s properties are copied over. If we want to replace a value we simply set it like normal. All properties that aren’t explicitly set will be copied over. This is a shallow copy. So value types are copied and reference types use pointers. So two records will "share" the memory location for reference type properties. In the past, this has caused some of you issues. Since records are immutable the thought is that the reference type won’t change for the life cycle of a given object. Ideally bugs won’t be introduced into your app because of state mutation and a shared reference.

Another benefit of records is when you print them they print their property values. No need to serialize them for readability purposes. You get that out of the box.

The ideal uses for records is going to center around immutability. Whenever you have a situation in your application where your objects are always going to be read only. Using records are going to offer a little more functionality, less boiler plate code, and protection against mutation. Think of data transfer objects, or if you have an application that is doing a lot of calculations. You may want to keep a historical state of the calculation. With records you can easily copy over the new state to a new object. Records are a nice addition to the language and I look forward to seeing what other use-cases I have for them.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s