DEV Community

Cover image for Creating and Working with Records in C# đŸ“č
ByteHide
ByteHide

Posted on

Creating and Working with Records in C# đŸ“č

Immutability is a concept that has been widely adopted in several programming languages. C# has been patiently waiting for a dedicated feature for this, and records are here!

In this article, we will explore C# records, ratify every myth and question surrounding records and classes, delve into the C# record constructor, reflect on inheritance, and understand how private records work in .NET. Let's go!

C# Records

Ever stumbled upon a piece of code filled with boilerplate just to make a class immutable? Good news; C# records to the rescue!

What are C# Records?

C# Records are a new kind of type declaration – they’re similar to classes, but they’re suited for developing immutable data models out of the box. Records, to put it simply, can be summarized as Simple-Immutables-Simplified.

Consider the following Record:

public record Person(string Name, int Age);
Enter fullscreen mode Exit fullscreen mode

You’ve just defined an immutable type named ‘Person’ with two properties, ‘Name’ and ‘Age’. That was easy, wasn’t it?

The Syntax of C# Records

The simple syntax of a record is yet another of its perks. Just like ‘classes’ and ‘structs’, you can declare ‘records’ with properties, methods, and events.

public record Car
{
    public string Brand { get; init; }
    public string Model { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

The ‘init’ keyword ensures the properties are immutable, i.e., once assigned, they can’t be changed.

But you might be wondering, what about value equality? Here’s another cool thing about records – they bring ‘value equality’ into play, rather than ‘reference equality’, which conventional classes use.

Delving into the Details: C# Record Types

Let’s break it down and dive deeper into the abyss of C# Record types.

Understanding the Structure of C# Record Types

Here’s the code-example illustrating the structure and key elements of a C# Record.

public record Employee
{
    public string Name { get; init; }
    public string Department { get; init; }
    public Employee Manager { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the Employee record consists of three properties. ‘Name’ and ‘Department’ are self-explanatory, but ‘Manager’ is a nested declaration within the Employee record – an Employee type inside Employee.

When to Use C# Records

Record in C# should be used when:

  • You need a type primarily defined by its data, and this data is immutable.
  • You need value equality – “Are these two things the same?” rather than “Are they the same object?”

So, isn’t it splendid how records encapsulate the essence of object-oriented programming? They’re quite the game-changer!

C# Record vs Class: A Comprehensive Reality Check

As Coders, we grapple with design decisions every day. Such a common predicament is when to use a class and when to opt for a record in C#. The fascinating world of programming languages is full of such dilemmas, and each choice can parley into diverse results.

Distinguishing the Functionality: C# Record vs Class

Indeed, C# records and classes are akin but have distinctive behaviours, especially around immutability and equality. But how about we get our hands dirty and write some code to unravel the differences?

public class CountryClass
{
    public string Name { get; set; }
    public string Continent { get; set; }
}

public record CountryRecord(string Name, string Continent);

CountryClass country1 = new CountryClass { Name = "Germany", Continent = "Europe" };
CountryClass country2 = new CountryClass { Name = "Germany", Continent = "Europe" };

Console.WriteLine(country1 == country2);  // Output: False
Enter fullscreen mode Exit fullscreen mode

The comparison of country1 & country2 returns false even though they have identical properties. It’s because classes in C# compare references, not the contents.

Now, let’s contrast this with records:

CountryRecord countryRecord1 = new CountryRecord("Germany", "Europe");
CountryRecord countryRecord2 = new CountryRecord("Germany", "Europe");

Console.WriteLine(countryRecord1 == countryRecord2);  // Output: True
Enter fullscreen mode Exit fullscreen mode

Wowza, it returns true. So, records in C# are different – they factor in the contents for comparison, not just reference! They are immutable and possess value semantics. Ain’t that nifty?

Realistic Use-Case Scenario: C# Record vs Class

Remember our old friend, the Product Catalogue, in an e-commerce application? Here’s where our recently learned knowledge comes into play. Let’s consider two classes, ProductClass and ProductRecord:

public class ProductClass
{
    public string ProductName { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    //...more properties
}

public record ProductRecord(string ProductName, string Description, decimal Price);
Enter fullscreen mode Exit fullscreen mode

For largely immutable entities like a product catalogue, employing records makes sense. You get a concise, immutable object with equal-to and copy semantics out of the box. You can also update properties using the with keyword while retaining the immutability:

var baseProduct = new ProductRecord("Book", "It's a book, duh!", 10m);
var discountedProduct = baseProduct with { Price = 9m };
Enter fullscreen mode Exit fullscreen mode

However, when objects require mutable state, classes still emerge victoriously. Imagine a Shopping Cart class in the same e-commerce application. You continually add, remove items, indicating a frequent change of state. Leveraging classes here is more fitting.

Each tool has its place. Isn’t that the philosophy of good programming?

Recorder C#: Automated Testing

We, coders, often find ourselves in a bind when dealing with UI tests. Wouldn’t it be great if we could just record our browser interactions and convert them into C# code for WebDriver-based UI tests? Well, that’s precisely what Recorder C# does for you!

var driver = new ChromeDriver();
var recorder = new Recorder(driver);
recorder.Start();

driver.Navigate().GoToUrl("http://www.yoursite.com");

//...do your testing

recorder.Stop();
Enter fullscreen mode Exit fullscreen mode

The Start method tells the recorder to start recording and the Stop method to stop. The rest is all your interactions with the UI!

Mastering the C# Record Constructor

A constructor’s primary function is to initialize the object of a class. But in C# records, a constructor does more than that!

Streamlining Constructors with C# Records

Records break the traditional mould of constructors. Just look at this tidy piece of code!

public record Employee(string Name, string Department);
Enter fullscreen mode Exit fullscreen mode

Here, in merely one line of code, you’ve given birth to a record with a constructor accepting two parameters and associating them to the read-only characteristics of the object. Talk about efficiency!

Crafting a C# Record Constructor

However, if your constructor requires additional logic or even validation of parameters, records can cover that too:

public record Employee
{
    public Employee(string name, string department)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
        if (string.IsNullOrEmpty(department)) throw new ArgumentNullException(nameof(department));

        Name = name;
        Department = department;
    }

    public string Name { get; }
    public string Department { get; }
}
Enter fullscreen mode Exit fullscreen mode

Need to validate input? You can add validation checks right inside the constructor. It’s like having your cake and eating it too (figuratively speaking)!

Understanding C# Record Inheritance

The Concept: C# Record Inheritance

Records inherit from other records. How cool is that? Records use the keyword ‘record’ instead of ‘class’ for inheritance. Take a look:

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

public record Truck : Vehicle
{
    public int TowingCapacity { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

Here, ‘Truck’ record is inheriting from the ‘Vehicle’ record. Talk about keeping the lineage clean!

Taking Advantage of C# Record Inheritance

Remember, records are sealed by default. If you want to allow other records to inherit from a base record, be sure to include the sealed modifier:

public sealed record Vehicle
{
    public string Brand { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

It’s stunning how much thought is put into extending and improving C# towards a more immutable future!

C# Record Property: Tweaking and Modifying

Let’s shed some light on how you can tweak and modify properties in C# Records.

Blessing of Immutability: C# Record Change Property

Records in C# are immutable, but there are times when you need a copy of a record with one or more properties modified. C# Record has the ‘with’ keyword for this. Magic, isn’t it?

public record Person(string Name, int Age);

Person bob = new Person("Bob", 30);
Person bobTwin = bob with { Name = "Bob's Twin" };  // New person with age 30
Enter fullscreen mode Exit fullscreen mode

Here, bobTwin is a new record, based on Bob, but it’s not Bob. Mind=blown!

Decoding the Differences: C# Record vs Record Struct

To struct, or not to struct, that’s the conundrum!

Understanding the Key Distinctions

So what’s the brass tacks of C# Record and Record Struct? The essential difference is that by design, record class types use reference semantics for equality while record struct types use value semantics for equality.

Making the Right Choice: C# Record vs Record Struct

When faced with the choice between record class and record struct, always go for the one that fits your scenario. When in doubt, favor record class. The semantics of a reference type better align with how data models are typically used.

Exploring Private Records in .NET

Private Records in C#: An introduction

Ever wished for private record types in C#? Your wish is C#’s command! This incredible addition of private accessibility modifier to record types makes things immeasurably simpler.

Application and Use Cases of Private Records in .NET

A private record is useful when seeking to encapsulate operations within a type, and its details play no role in its public API. This makes it an ideal candidate for implementing Data Transfer Object (DTO) patterns and encapsulating algorithms.

Congratulations, folks! You’ve made it to the end of this enthralling journey through the vast landscape of C# records. We’ve covered everything from the basics, to scenarios for usage, to comparisons with classes, and we’ve even touched on C# Record Constructors, inheritance, property changes, and private records in .NET.

Now, it’s time for you to apply these concepts and take advantage of this wonderful feature of C#.

Remember, though records are awesome, they’re not a silver bullet for all scenarios. Sometimes, classes or structs might be just what you need.

Think it through, always opt for the simplest solution that meets your need.

Top comments (4)

Collapse
 
b33tl3 profile image
CĂ©dric

very nice article, thx !!
But I don"t really understand why the part "Recorder C#: Automated Testing" is here (even if it is interesting)
and the end, record struc vs record class should be better explained, IMHO.

thx again anyway !! :)

Collapse
 
b33tl3 profile image
CĂ©dric • Edited

I ask question to Copilot. Here are the response. Could help other people.

The difference between a record class and a record struct in C# mainly revolves around whether they are reference types or value types:

  • Record Class:

    • Reference Type: This means that when you create an instance of a record class, it is stored on the heap, and variables hold a reference to the memory location.
    • Immutability: By default, record classes are immutable, meaning their properties cannot be changed after they are created. However, you can make them mutable if needed.
    • Usage: Use record classes when you need to represent complex data structures that benefit from reference semantics, such as when you need to share data between different parts of your program.
  • Record Struct:

    • Value Type: This means that instances of record structs are stored on the stack (if they are local variables) or inline within other objects, and variables hold the actual data.
    • Mutability: Record structs are mutable by default, but you can make them immutable by using the readonly keyword.
    • Usage: Use record structs for small, lightweight data structures that benefit from value semantics, such as when you need to pass data around without the overhead of heap allocation.

In summary, choose record classes for more complex, reference-based data structures and record structs for simpler, value-based data structure

After that, I asked :

I thought that records were immutable. Why record structs are mutable by default ?

That's a great question! While records in C# are often associated with immutability, the default mutability of record structs is due to their nature as value types.

Here's a bit more detail:

  • Record Classes: These are reference types and are immutable by default. This immutability is a key feature that helps ensure that the data they hold cannot be changed after creation, which is useful for creating reliable and predictable data structures.

  • Record Structs: These are value types and are mutable by default. This default mutability is because value types are typically used for small, simple data structures where performance is critical, and the overhead of creating new instances for every change can be significant. However, you can make record structs immutable by using the readonly keyword.

If you want to make a record struct immutable, you can define it like this:

public readonly record struct Point(int X, int Y);
Enter fullscreen mode Exit fullscreen mode

This way, the properties X and Y cannot be changed after the Point is created.

Collapse
 
artydev profile image
artydev

Thank you :-)

Collapse
 
jangelodev profile image
JoĂŁo Angelo

Top, very nice and helpful !
Thanks for sharing. âœ