If you've been writing C# for more than a few years, you've written thousands of DTOs. Request models, response models, event payloads, integration contracts, the connective tissue of every distributed system. And if you're like most of us, you've written nearly all of them as classes with { get; set; } properties, because that's what we've done since .NET Framework 2.0.

Here's the uncomfortable truth: a mutable class is the wrong data structure for a DTO, and it has been since C# 9 gave us a better one. Not "stylistically wrong." Wrong in ways that show up as production incidents, duplicate payments, corrupted audit trails, and race conditions in async pipelines.

This article makes the case from first principles, with examples drawn from banking and fintech systems, payment processing, reconciliation, maker-checker workflows, because that's where the cost of getting this wrong is measured in money, not just bugs. Whether you're a junior engineer writing your first API contract or a solutions architect reviewing a payment platform design, the goal is the same: understand why records exist, not just how to type them.

Imagine you're building a ride-sharing app like Uber or Bolt. When the frontend requests a list of nearby drivers, you return a DriverDto: