Record equality and nondestructive updates solve different problems
A record is a type declaration that supplies useful data-oriented behavior, including generated value equality. For this project's sealed Order record, equality compares its Id, Amount, and IsPaid values. A with expression supplies a different feature: create another record based on an existing one while changing named members. These features often work well together, but equality does not itself perform copying, and copying does not guarantee that the revised values remain equal to the original.
The model already exists in Models/Order.cs as a positional record. Its generated properties support initialization and with expressions. Preserve that model file and do not redeclare Order in Program.cs. The three members here are a string, a decimal, and a bool. They do not expose a mutable nested collection, so the shallow nature of a record-class with copy does not create shared mutable child state in this particular example. Other record models can have such sharing and need a separate ownership policy.
Keep the original, the revision, and the equal value distinct
The first Order has id a, amount twelve, and paid status true. The with expression constructs changed using that record as a starting point and replaces only Amount with four. It does not assign four into paid.Amount. The first two WriteLine calls therefore print 12 and 4. Both records retain id a and paid status true, but their different amounts mean they no longer have the same complete value.
The final expression constructs another Order with the same three values as paid. The generated record equality makes paid == new Order("a", 12m, true) true, so the third output line is True. This comparison is not ReferenceEquals: two separately constructed record-class instances can compare equal by value. Conversely, an ordinary class without an equality contract does not automatically acquire the same behavior just because its fields happen to look similar. Inspect the declared type rather than generalizing from the punctuation of ==.
var changed = paid with { Amount = 4m };
Console.WriteLine(paid.Amount);
Console.WriteLine(changed.Amount);A with copy is not a universal deep clone
If a future record property contains a mutable list, the generated with copy normally shares that list reference unless you explicitly create another list or use a suitably immutable model. Get-only or init-only access also does not freeze the contents of a nested object. Additionally, this Order constructor does not validate a nonnegative price by itself. Value equality and convenient updates do not supply every business invariant; decide which states are valid and ensure construction and update paths preserve those rules.
Compare the revision, then change another declared value
First append Console.WriteLine(paid == changed). Predict False because twelve and four are different Amount values. Add a ReferenceEquals comparison between paid and a separately constructed equal Order and predict False there too. Hint: value equality and object identity ask different questions, so True for one does not contradict False for the other. Keep the original three output lines separate from these additional diagnostic observations.
Next create another revision that changes only IsPaid to false and retains the original amount. Predict that paid still has true and amount twelve, while the revision has false and amount twelve; they compare unequal. Hint: with changes only the members named in its initializer. Finally add a mutable-list property in a separate model experiment and show how changing that shared child differs from replacing an immutable decimal property during construction.
Track which operation changes which guarantee
Explain copying, member replacement, value equality, and reference identity as separate operations. For the unchanged project, retain the exact three-line expectation: twelve, four, and True. When considering a record for a larger model, inspect all reachable members and its validation requirements rather than treating record as a synonym for deeply immutable, automatically valid data. A good test checks both preservation of the original value and the specific differences introduced by an update.
- The original paid record keeps Amount twelve.
- The with revision has Amount four in another instance.
- A separately constructed equal Order produces the original
True. - Nested mutable references need their own copying or ownership policy.
A working example
var paid = new Order("a", 12m, true);
var changed = paid with { Amount = 4m };
Console.WriteLine(paid.Amount);
Console.WriteLine(changed.Amount);
Console.WriteLine(paid == new Order("a", 12m, true));
with builds a copy. It does not edit the original.