Menu

Using C# Record Types for Immutable Data Models

Introduced in C# 9.0, record types offer a concise way to create immutable data models with value-based equality. They simplify many common programming tasks when working with data-centric classes.

What Are Record Types?

Records are reference types (like classes) but with built-in functionality for representing immutable data:

// Traditional class approach
public class PersonClass
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
    
    // Requires manual implementation of equality, hash code, etc.
}

// Equivalent record
public record Person(string FirstName, string LastName);

This simple declaration creates an immutable type with:

  • Constructor that accepts all properties
  • Public, init-only properties
  • Value-based equality (compares property values, not references)
  • ToString() implementation that displays all properties
  • Deconstruction support

Benefits of Using Records

1. Immutability by Default

Records are designed for immutability, making them perfect for:

  • Domain models
  • DTOs (Data Transfer Objects)
  • API responses
  • Configuration objects
var person = new Person("John", "Doe");
// person.FirstName = "Jane"; // Compile error - properties are init-only

2. Non-Destructive Mutation with 'with' Expressions

Need to change a property? Use the 'with' expression:

var person = new Person("John", "Doe");
var updatedPerson = person with { FirstName = "Jane" };

// person still refers to "John Doe"
// updatedPerson refers to "Jane Doe"

3. Value-Based Equality

Records automatically implement value equality:

var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");

Console.WriteLine(person1 == person2); // True
Console.WriteLine(person1.Equals(person2)); // True

4. Easy Class Hierarchies

Records can inherit from other records:

public record Person(string FirstName, string LastName);
public record Employee(string FirstName, string LastName, string Department) 
    : Person(FirstName, LastName);

When to Use Records

Use records when:

  • You need immutable objects
  • Equality should compare values, not references
  • You're creating simple data containers
  • You need non-destructive updates with the 'with' expression

Use traditional classes when:

  • You need mutable properties
  • You need reference-based equality
  • You need more control over property implementation

Performance Considerations

While records are convenient, be aware that:

  • The 'with' expression creates a new object (memory allocation)
  • Comparing large records can be slower than reference equality

Example: API Data Model

// API response model
public record WeatherForecast(
    DateTime Date,
    int TemperatureC,
    string Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

// Usage
var forecasts = await httpClient.GetFromJsonAsync<List<WeatherForecast>>("weatherforecast");

Records are a powerful addition to C#, making it easier to create robust data models with less boilerplate code.

1
56

Related

When working with URLs in C#, encoding is essential to ensure that special characters (like spaces, ?, &, and =) don’t break the URL structure. The recommended way to encode a string for a URL is by using Uri.EscapeDataString(), which converts unsafe characters into their percent-encoded equivalents.

string rawText = "hello world!";
string encodedText = Uri.EscapeDataString(rawText);

Console.WriteLine(encodedText); // Output: hello%20world%21

This method encodes spaces as %20, making it ideal for query parameters.

For ASP.NET applications, you can also use HttpUtility.UrlEncode() (from System.Web), which encodes spaces as +:

using System.Web;

string encodedText = HttpUtility.UrlEncode("hello world!");
Console.WriteLine(encodedText); // Output: hello+world%21

For .NET Core and later, Uri.EscapeDataString() is the preferred choice.

28
1298

In C#, you can format an integer with commas (thousands separator) using ToString with a format specifier.

int number = 1234567;
string formattedNumber = number.ToString("N0"); // "1,234,567"
Console.WriteLine(formattedNumber);

Explanation:

"N0": The "N" format specifier stands for Number, and "0" means no decimal places. The output depends on the culture settings, so in regions where , is the decimal separator, you might get 1.234.567.

Alternative:

You can also specify culture explicitly if you need a specific format:

using System.Globalization;

int number = 1234567;
string formattedNumber = number.ToString("N0", CultureInfo.InvariantCulture);
Console.WriteLine(formattedNumber); // "1,234,567"
4
462

Storing passwords as plain text is dangerous. Instead, you should hash them using a strong, slow hashing algorithm like BCrypt, which includes built-in salting and resistance to brute-force attacks.

Step 1: Install BCrypt NuGet Package

Before using BCrypt, install the BCrypt.Net-Next package:

dotnet add package BCrypt.Net-Next

or via NuGet Package Manager:

Install-Package BCrypt.Net-Next

Step 2: Hash a Password

Use BCrypt.HashPassword() to securely hash a password before storing it:

using BCrypt.Net;

string password = "mySecurePassword123";
string hashedPassword = BCrypt.HashPassword(password);

Console.WriteLine(hashedPassword); // Output: $2a$12$...

Step 3: Verify a Password

To check a user's login attempt, use BCrypt.Verify():

bool isMatch = BCrypt.Verify("mySecurePassword123", hashedPassword);
Console.WriteLine(isMatch); // Output: True

Ensuring proper hashing should be at the top of your list when it comes to building authentication systems.

3
273