About this book

This book introduces immutable data design in C# as a way to make state easier to understand and debug as data moves through a program. You will see how to use record, init, required, with expressions, immutable collections, and pattern matching to replace “modifying objects” with data transformations that are easier to trace, test, and maintain.

The focus is on design choices in existing C# projects. Which data should remain immutable? Where does a mutable object remain the better choice? When data passes through APIs, databases, background workflows, and tests, how can you design types that make the code more reliable?

Target readers

This book is for developers who already have C# experience and want to improve their data models, API design, and state management.

Prerequisites

Before reading, you should have some C# development experience and have used the following features:

  • Object-oriented fundamentals: classes, interfaces, inheritance, and encapsulation.
  • Modern C# syntax and features: LINQ, asynchronous programming with async/await, and nullable reference types.

You do not need a background in functional programming or advanced type theory. You also do not need prior familiarity with Domain-Driven Design (DDD) or a particular layered architecture. When the book uses common terms such as domain model, application layer, and repository, it briefly explains them on first use. You can also consult the glossary for the book’s main terms.

When this book can help

If you have encountered any of the following situations, this book offers practical ways to improve your design:

  • An object is modified by several methods, services, or system layers, making it difficult to determine where it changed or who broke it.
  • You know record, init, with, and required, but are unsure how to combine them in a real system.
  • When designing Web API request and response DTOs, you are unsure whether to use a mutable class, a record class, or a readonly struct.
  • You understand the benefits of immutable data but are unsure how to update deeply nested data structures effectively.
  • You want to balance immutable design with framework constraints such as ORMs (including Entity Framework Core), JSON serialization, UI data binding, or performance-sensitive code.

Book structure

The book has six chapters, using an order-processing system as its main running example. Orders involve several common design concerns: state transitions, API input and output data, collections, validation, persistence, and state modeling.

  • Chapter 1 — From mutable objects to immutable data: See why mutable state is hard to track, and learn how to replace “modifying an object in place” with “creating a new version from the old one.”
  • Chapter 2 — C# records, init, and immutable collections: Learn the syntax and collections needed to build immutable data, and recognize common pitfalls that leave a record mutable.
  • Chapter 3 — Replacing in-place mutation with data transformation: Use order workflows to explore transformations, business-rule validation, and external dependencies, and consider whether behavior belongs inside a type or in an external service.
  • Chapter 4 — Data models for APIs, DTOs, and system boundaries: Apply immutable design to API data contracts and configuration objects, organizing validation, serialization, and mapping between DTOs and domain models.
  • Chapter 5 — Trade-offs and adoption of immutable design: Assess suitable use cases and costs, choose a starting point for gradual adoption, and review whether an AI-proposed refactoring solves a real problem.
  • Chapter 6 — Optional advanced reading: ruling out invalid states with types: When a single record and enum no longer express valid states well, evaluate the benefits and adoption costs of state-specific types and union types, and compare the approaches in C# 14 and C# 15.

Appendix A uses an open-source text-to-braille system to show how to narrow the scope of mutation when code depends on mutable models, object references, and existing serialization formats. It is intended for readers preparing to refactor their own projects.

Reading suggestions

Read Chapters 1 through 5 in order to complete the book’s core learning path. Chapter 6 is optional advanced reading. If your current state model is already clear and reliable, you can go straight to the afterword. When adopting these ideas in an existing project, you can also consult Appendix A.

When reading the code, focus on the design choices behind each example, not just the syntax. Immutable data design is not about making every property read-only. It is about understanding how data is created and used, how its state changes, and which parts of the program are responsible for those changes.

To practice, download the sample code and run the tests as you follow each chapter’s refactoring steps. If you see an opportunity to apply these ideas in your own project, choose a small data flow to refactor. You can ask an AI agent to help, then review the changes and run the relevant tests. You do not need to do everything at once. Making one group of DTOs, one value object, or one data flow more reliable can deliver practical benefits.

Reading devices: A computer or tablet is recommended for a better experience with code and diagrams. If you use an e-reader, choose one with a larger screen.

Technical environment and development tools

To follow the exercises and run the sample code, you will need the following:

  • Core environment (Chapters 1–5): The examples are based on C# 14 and .NET 10. Most design principles and language features discussed also apply to C# 10 (.NET 6) and later. However, some examples use newer syntax or APIs and need adjustments for older versions. For example, the [] collection expressions in Chapter 1 require C# 12 or later. The sample projects for Chapters 1–5 target .NET 10.
  • Optional advanced environment (Chapter 6): Chapter 6 uses C# 15 syntax (union types and closed hierarchies), requiring a .NET 11 SDK that supports C# 15. Section 6.3 also provides a state-record hierarchy example that runs on C# 14 / .NET 10 and explains how its inheritance restrictions and exhaustiveness checks differ from those of a union.
  • Development tools: Visual Studio 2026, Visual Studio Code, JetBrains Rider, or another IDE/editor that supports modern .NET.

Sample code

The sample code is available on GitHub:

https://github.com/ippobooks/csharp-immutable-data-design-samples