Choosing Between Record, Class, and Struct in C# 9.0: A Comprehensive Guide

Dec 02, 2025 · Programming · 26 views · 7.8

Keywords: C# Record | Data Type Selection | Immutability | Value Semantics | Performance Optimization

Abstract: This article provides an in-depth analysis of the Record type introduced in C# 9.0, comparing it with traditional Class and Struct types. By explaining the differences between value types and reference types, and highlighting Record's immutability and value semantics, the article offers practical guidance for selecting appropriate data types in real-world development. It focuses on Record's advantages in scenarios like DTOs and API request bindings, demonstrates its copying mechanisms through code examples, and discusses performance considerations to help developers make informed technical decisions.

Introduction

With the introduction of record in C# 9.0, developers now have a new way to model data. Compared to traditional class and struct types, record offers unique advantages in specific scenarios. This article aims to deeply analyze the core characteristics of these three types and provide practical selection guidelines to help developers make sound technical decisions in real projects.

Data Type Fundamentals: Value Types vs Reference Types

In C#, struct is a value type, while class and record are reference types. Value types are allocated on the stack and store data directly, while reference types are allocated on the heap and store references to data. This fundamental difference affects performance, memory management, and behavior.

For struct, Microsoft officially recommends using it when all the following conditions are met:

  1. Logically represents a single value, similar to primitive types (like int, double)
  2. Instance size is under 16 bytes
  3. Is immutable
  4. Will not have to be boxed frequently

If these conditions are not met, reference types should be considered.

Core Characteristics of Record

record is a reference type introduced in C# 9.0 but with value semantics. Its main features include:

The following example demonstrates basic record usage:

public record Person(string FirstName, string LastName);

// Creating modified copies using with expressions
var original = new Person("John", "Doe");
var modified = original with { LastName = "Smith" };

Practical Application Scenarios

Data Transfer Objects (DTOs)

For DTOs that transfer data between controller and service layers, record is an ideal choice. Its immutability ensures data safety during transmission, while value semantics simplifies equality comparisons.

// Using record as DTO
public record SearchParameters(string Query, int PageSize, int PageNumber);

public class SearchController : Controller
{
    public async Task<IActionResult> Search(SearchParameters parameters)
    {
        // parameters is immutable, ensuring data safety
        var results = await _service.SearchAsync(parameters);
        return Ok(results);
    }
}

API Request Binding

In ASP.NET Core APIs, using record as request models ensures the immutability of request data, which is ideal for API design.

// Request binding example
public record CreateUserRequest(string Username, string Email, int Age);

[HttpPost]
public IActionResult CreateUser(CreateUserRequest request)
{
    // request is immutable, preventing accidental modifications in subsequent processing
    var user = _mapper.Map<User>(request);
    _repository.Add(user);
    return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}

Copying Mechanisms Explained

The copying behavior of record requires special attention. When creating copies using with expressions:

The following example demonstrates copying behavior in different scenarios:

public record ExampleRecord(List<string> Items, int Count);

var original = new ExampleRecord(new List<string> { "a", "b" }, 2);
var copy = original with { Count = 3 };

// Modifying the original list affects the copy
original.Items.Add("c");
Console.WriteLine(copy.Items.Count); // Output: 3
Console.WriteLine(ReferenceEquals(original.Items, copy.Items)); // Output: True

Performance Considerations

While record provides convenient syntax and semantics, it should be used cautiously in performance-sensitive scenarios. Creating new instances with with expressions involves copying operations, which may incur performance overhead for large objects.

Benchmark tests show that traditional class may have slight performance advantages in scenarios requiring frequent creation of modified copies:

// Example benchmark results
// TestRecord: 120.19 μs
// TestClass: 98.91 μs

However, in most practical applications, this difference is usually negligible, especially when operations involve databases or file systems where external operation latency far exceeds memory copying overhead.

Selection Guidelines Summary

Based on the above analysis, the following selection guidelines can be summarized:

  1. When to use struct: When the data type meets all value type conditions, particularly for small, immutable, value-semantic data.
  2. When to use record:
    • Immutable data transfer objects are needed
    • API request/response models
    • Any reference type requiring value semantics
    • Unidirectional data flow scenarios
  3. When to use class:
    • Mutable state is required
    • Complex object hierarchies
    • Identity rather than value equality is needed
    • Performance-critical scenarios requiring frequent modifications

Conclusion

The introduction of record provides C# developers with a new tool particularly suited for handling immutable data models. In practical development, appropriate data types should be selected based on specific requirements: struct for lightweight value types, record for immutable reference type data, and class for traditional mutable object models. Understanding the core differences and application scenarios of these types will help write safer, more maintainable code.

As the C# language continues to evolve, new features like anonymous records will further enrich data modeling options. Developers should stay informed about language innovations while making reasonable technical choices based on specific project requirements.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.