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
55

Related

String interpolation, introduced in C# 6.0, provides a more readable and concise way to format strings compared to traditional concatenation (+) or string.Format(). Instead of manually inserting variables or placeholders, you can use the $ symbol before a string to directly embed expressions inside brackets.

string name = "Walt";
string job = 'Software Engineer';

string message = $"Hello, my name is {name} and I am a {job}";
Console.WriteLine(message);

This would produce the final output of:

Hello, my name is Walt and I am a Software Engineer

String interpolation can also be chained together into a multiline string (@) for even cleaner more concise results:

string name = "Walt";
string html = $@"
    <div>
        <h1>Welcome, {name}!</h1>
    </div>";
37
148

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
426

When working with SQL Server, you may often need to count the number of unique values in a specific column. This is useful for analyzing data, detecting duplicates, and understanding dataset distributions.

Using COUNT(DISTINCT column_name)

To count the number of unique values in a column, SQL Server provides the COUNT(DISTINCT column_name) function. Here’s a simple example:

SELECT COUNT(DISTINCT column_name) AS distinct_count
FROM table_name;

This query will return the number of unique values in column_name.

Counting Distinct Values Across Multiple Columns

If you need to count distinct combinations of multiple columns, you can use a subquery:

SELECT COUNT(*) AS distinct_count
FROM (SELECT DISTINCT column1, column2 FROM table_name) AS subquery;

This approach ensures that only unique pairs of column1 and column2 are counted.

Why Use COUNT DISTINCT?

  • Helps in identifying unique entries in a dataset.
  • Useful for reporting and analytics.
  • Efficient way to check for duplicates.

By leveraging COUNT(DISTINCT column_name), you can efficiently analyze your database and extract meaningful insights. Happy querying!

1
115