Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
C#

C# Record Types: Immutable Data Patterns That Eliminate Bugs

- 05.02.26 - ErcanOPAK

Mutable objects causing threading issues? C# 9+ records provide immutable data structures with built-in value semantics.

Records vs Classes: When to Use Each

// Use RECORDS for:
// 1. Immutable data transfer objects (DTOs)
// 2. Value objects in domain-driven design
// 3. Configuration objects
// 4. API request/response models
// 5. Event objects in event sourcing

// Use CLASSES for:
// 1. Entities with identity and mutation
// 2. Services with behavior
// 3. Complex objects with inheritance hierarchies
// 4. Objects needing custom equality semantics
// 5. Framework base classes

// Example: Record for API response
public record ApiResponse(
    T Data,
    bool Success,
    string Message,
    DateTime Timestamp = default)
{
    public DateTime Timestamp { get; init; } = Timestamp == default 
        ? DateTime.UtcNow 
        : Timestamp;
}

// Usage:
var response = new ApiResponse(
    Data: product,
    Success: true,
    Message: "Product retrieved successfully");
    
// Immutable - cannot modify after creation
// Value equality - two responses with same data are equal
// Built-in ToString() with property values
// Deconstruction support

Advanced Record Patterns:

// Positional record (immutable by default)
public record Person(
    string FirstName,
    string LastName,
    int Age);

// With non-positional properties
public record Employee : Person
{
    public string EmployeeId { get; init; }
    public decimal Salary { get; init; }
    public Department Department { get; init; }
    
    public Employee(
        string firstName,
        string lastName,
        int age,
        string employeeId,
        decimal salary,
        Department department)
        : base(firstName, lastName, age)
    {
        EmployeeId = employeeId;
        Salary = salary;
        Department = department;
    }
}

// Record struct (C# 10+)
public readonly record struct Point(
    double X,
    double Y)
{
    public double DistanceFromOrigin => Math.Sqrt(X * X + Y * Y);
}

// With validation in init-only setters
public record OrderItem
{
    private int _quantity;
    private decimal _price;
    
    public int Quantity
    {
        get => _quantity;
        init
        {
            if (value <= 0)
                throw new ArgumentException("Quantity must be positive");
            _quantity = value;
        }
    }
    
    public decimal Price
    {
        get => _price;
        init
        {
            if (value < 0)
                throw new ArgumentException("Price cannot be negative");
            _price = value;
        }
    }
    
    public decimal Total => Quantity * Price;
}

Related posts:

ASP.NET Core “Middleware Never Executes” — The Ordering Trap

async void — The Exception Black Hole

Lightning-Fast Lookups in .NET Using MemoryCache

Post Views: 3

Post navigation

.NET Core Middleware Magic: How to Build Pipeline Filters That Transform Your API
Ajax Revolution: How Fetch API and Async/Await Replace jQuery.ajax()

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com