Record types in C# explained

Dive into C# 9 record types, your coding allies for simplifying tasks and ensuring constancy. Explore blueprints, "init" expressions, and real-life examples for cleaner and smarter code.

Introduction 

If you've heard about "record types" and wondered how they fit into your coding journey, you're in the right place. In this beginner-friendly guide, we'll explore what record types are, why they're essential, and how you can leverage their power with ease.   

What are Record Types? 

In simple terms, record types are like blueprints for creating objects in your code. Imagine you want to represent a person – instead of writing long-winded code to define their attributes, C# 9 lets you create a record type effortlessly: 

public record Person(string FirstName, string LastName); 

Here, with just one line of code, you've defined a blueprint for a person with first and last names. It not only sets up the structure but also automatically generates useful methods like ‘Equals’ and  ‘ToString’. 

Why constancy matters 

Constancy is a central concept in C# record types. 
Once you create an instance of a record, it’s values remain constant. Let’s take our  ‘Person’ example: 

var person = new Person(“John”, “Doe”); 

Once we've set John's first and last names, we can't change them later. It might sound strict, but it's like ensuring the foundation of a house stays strong – we don't want the walls moving around! 

Attempting to change the values directly will result in a compile error. 

person.FirstName = "Jane"; // Compile error: Cannot assign to 'FirstName' because it is a readonly property 

Alternatively, attempting to create a new person with different values will also result in an error. 

person = new Person("Jane", "Smith"); // Compile error: Cannot assign to 'person' because it is a readonly variable 

Simplifying coding tasks 

One of the advantages of record types is their ability to generate common methods automatically. Consider this scenario: 

var person1 = new Person(“John”, “Doe”); 
var person2 = new Person(“John”, “Doe”); 
Console.WriteLine(person1.Equals(person2)); // true 

The ‘Equals’ method is created for us, checking if two persons are equal based on their attributes. This means less manual coding and more focus on what our program needs to accomplish. 

With the overridden ‘==’ operator, we can easily check if two ‘Person’ records are the same by using the straightforward syntax: ‘if (person1 == person2)’. This allows us to compare the contents of the objects effortlessly. 

if (person1 == person2){ 
       Console.WriteLine("Both persons are equal"); 
       // Do something here 
} else { 
            Console.WriteLine("Persons are not equal"); 
} 

Controlling property initialization 

C# 9 introduces "init" expressions, providing control over property initialization. Let's set our ‘Person’ example: 

public record Person { 
public string FirstName { get; init; } 
public string LastName { get; init; } 
} 
var person = new Person { FirstName = “John”, LastName = “Doe” }; 

With "init" we can set properties when creating the person, but trying to change them later will result in an error. It's like setting the rules for our data upfront.  

Tuple deconstruction 

public record Person(string FirstName, string LastName); 
var person = new Person("John", "Doe"); 

Tuple deconstruction

(var firstName, var lastName) = person; 
Console.WriteLine($"First Name: {firstName}"); 
Console.WriteLine($"Last Name: {lastName}"); 

In this example, the ‘Person’ record is created with values "John" and "Doe". The tuple deconstruction ‘(var firstName, var lastName) = person;’ is used to extract the values of ‘FirstName’ and ‘LastName’ from the ‘person’ record, and then those values are printed to the console.  

The output will be: 

First Name: John 
Last Name: Doe

This syntax allows you to easily extract values from a record or tuple and use them as individual variables.  

With expression 

In C#, you can use the ‘with’ expression with records to create a new record that is a copy of an existing one with some fields modified.  

Here's an example: 

var person = new Person("John ", "Doe"); 

Creating a new record based on the existing one with modified values 

var personsKid= person with { FirstName = "Alice" }; 

Displaying the original and modified records 

Console.WriteLine("Original Person: " + person.ToString()); 
Console.WriteLine("Modified Person's Kid: " + personsKid.ToString()); 

In this example, a new ‘Person’ record (‘personsKid’) is created based on the existing ‘person’ record with the ‘FirstName’ field modified to " Alice ".  The output will be: 

Original Person: Person { FirstName = John, LastName = Doe } 
Modified Person's Kid: Person { FirstName = Alice, LastName = Doe } 

This showcases the immutability of records and how the ‘with’ expression allows you to create a modified copy of a record with specific fields changed.  

Real-life examples  

Employee records 

public record Employee(string FirstName, string LastName, DateTime HireDate); 

Here constancy ensures that once we create an employee record, their hire date remains consistent, maintaining accurate historical data.

Configuration settings  

public record AppConfig(string ApiKey, string BaseUr, int TimeoutInSeconds); 

In this case, record types simplify handling configuration settings, ensuring they stay unchanged once initialized. 

Enhancing API responses 

public record ApiResponses<T>(bool IsSuccess, T Data, string ErrorMessage); 

Record types stand out in API responses, providing a structured and constant representation, making error handling more straightforward.

Diving deeper with more examples 

Financial transactions 

public record Transaction(string TransactionId, decimal Amount, DateTime TransactionDate); 

When dealing with financial transactions, maintaining the constancy of transaction details ensures the integrity of financial records.  

Getting paid 

When you get your salary, record types keep a clear and unchanging record of the transaction details, like how much you earned and when you received it. 

public record SalaryPayment { 
    public string EmployeeId { get; init; } 
    public decimal Amount { get; init; } 
    public DateTime PaymentDate { get; init; } 
} 
var johnsSalary = new SalaryPayment { EmployeeId = "12345", Amount = 5000, PaymentDate = DateTime.Now }; 

Shopping online 

Making an online purchase involves financial transactions. Record types ensure the details, such as what you bought, how much you spent, and when you made the purchase, stay steady and reliable. 

public record OnlinePurchase { 
    public string PurchaseId { get; init; } 
    public decimal AmountSpent { get; init; } 
    public DateTime PurchaseDate { get; init; } 
} 
var shoppingCart = new OnlinePurchase { PurchaseId = "98765", AmountSpent = 150, PurchaseDate = DateTime.Now }; 

Paying back a Loan 

If you're repaying a loan, record types maintain a consistent record of each payment, ensuring the transaction details remain stable over time. 

public record LoanRepayment { 
    public string LoanAccountId { get; init; } 
    public decimal InstallmentAmount { get; init; } 
    public DateTime PaymentDate { get; init; } 
} 
var loanPayment = new LoanRepayment { LoanAccountId = "54321", InstallmentAmount = 200, PaymentDate = DateTime.Now }; 

Setting bills  

Whether it's paying your electricity or water bills, record types keep a clear and unchanging record of your payments, like the amount and when you paid. 

public record UtilityBillPayment { 
    public string BillId { get; init; } 
    public decimal AmountPaid { get; init; } 
    public DateTime PaymentDate { get; init; } 
} 
var electricityBill = new UtilityBillPayment { BillId = "78901", AmountPaid = 80, PaymentDate = DateTime.Now }; 

Investment earnings 

For investments like stocks, record types secure the details of your earnings, such as dividends, making sure the recorded information remains constant. 

public record InvestmentDividends { 
    public string InvestmentId { get; init; } 
    public decimal AmountReceived { get; init; } 
    public DateTime PaymentDate { get; init; } 
} 
var stockDividends = new InvestmentDividends { InvestmentId = "45678", AmountReceived = 50, PaymentDate = DateTime.Now }; 

In each example, the record structure captures specific details related to the financial transaction. These records ensure simplicity, constancy, and reliability when dealing with different financial scenarios.

User preferences 

public record UserPreferences(bool DarkModeEnabled, int FontSize, string ThemeColor); 

For applications with user customization, record types guarantee that once preferences are set, they remain unaltered throughout the user's session.

Products in stock  

When you use record types to talk about your products, like their special code, name, and how many you have, it means once you add a product to your list, the important details won't unexpectedly change. 

In simpler terms, using record types for products is like having a solid plan to keep your store's inventory organized and reliable. 

public record Product(string ProductCode, string ProductName, int QuantityInStock); 

In managing product inventory, constancy ensures that once a product is added, its key details, like code and name, stay consistent.  

Conclusion 

In conclusion, C# 9's record types are your supporters in simplifying data handling. Think of them as blueprints that not only make your code cleaner but also spare you from writing repetitive tasks. Embrace the concept of keeping things constant, control changes wisely, and you'll find yourself building powerful and maintainable software with ease.  

Explore other articles

explore