Summary#
with copies a class value and replaces the fields you name:
class Money {
public decimal Amount;
public string Currency = "USD";
public string Note = "";
}
string Rename() {
var fee = new Money { Amount = 25m, Currency = "SEK", Note = "late fee" };
var refund = fee with { Amount = 0m };
return refund.Currency; // "SEK" — carried over, not re-defaulted
}refund is a new value. It took its Amount from the initializer and everything else — Currency, Note —
from fee, which is unchanged.
Signature#
value with { Field = expr, … } // a copy of `value`, with those fields replaced
value with { } // a plain copy
a with { X = 1m } with { Y = 2m } // chains; each copy is made from the one beforewith is available on any class. It is not available on an entity, or on a scalar — see
[[#errors|Errors]].
Description#
The fields you do not name come from the value you copied#
This is the whole point, and it is the part worth testing when something looks wrong. A with does not start
from the class's declared defaults and it does not re-run field initializers:
class Settings {
public string Theme = "light";
public decimal Zoom = 1m;
}
string Keep() {
var dark = new Settings { Theme = "dark", Zoom = 2m };
var zoomed = dark with { Zoom = 3m };
return zoomed.Theme; // "dark" — the copy's value, NOT the declared "light"
}The original is never modified#
with produces a value; it does not write through to the one it copied. Anything else still holding the original
sees exactly what it saw before:
class Money { public decimal Amount; public string Note = ""; }
decimal Both() {
var original = new Money { Amount = 10m, Note = "invoice" };
var adjusted = original with { Amount = 25m };
return original.Amount + adjusted.Amount; // 35 — 10 and 25, two values
}A copy keeps the type it actually is#
If you copy a value held in a base-typed variable, the copy is the type the value is, not the type the variable is declared as:
class Animal { public string Name = "?"; }
class Dog : Animal { public decimal Legs = 4m; }
bool StillADog() {
Animal held = new Dog { Name = "rex", Legs = 3m };
var renamed = held with { Name = "fido" };
return renamed is Dog; // true, and its Legs is still 3
}On a record, and on a class#
with is C#'s copy for a record and a struct. Osy# also allows it on a plain
class, where it makes a new INSTANCE with the same members. What the copy equals follows from what it is: a record
compares by its members, so a plain copy equals the value it came from, while a class copy is a
second instance and does not.
record Point(decimal X, decimal Y);
class Marker { public decimal X; }
string Copies() {
var p = new Point(1m, 2m);
var m = new Marker { X = 1m };
return $"{p == p with { }} {m == m with { }}"; // "True False"
}readonly fields still cannot be set#
A readonly field is one only the constructor may assign, and a with is not a constructor — it
copies an already-built value and then writes over it. To vary a readonly field, construct the value instead.
Examples#
class Quote {
public string Customer;
public decimal Net;
public decimal Vat = 0m;
public string Status = "draft";
}
decimal Finalize() {
var draft = new Quote { Customer = "Acme", Net = 100m };
var taxed = draft with { Vat = 25m };
var sent = taxed with { Status = "sent" };
// Customer and Net rode through both copies; nobody had to restate them.
return sent.Net + sent.Vat; // 125
}Errors#
| Message | Cause | Fix |
|---|---|---|
| with` copies an object, and a `decimal` is not one` | The value on the left is a scalar, which has no fields to replace. | Use ordinary arithmetic or assignment; `with` applies to a class. | | withcopies an in-memoryclass, and 'X' is an entity`` | The value on the left is a stored row. Copying one would silently mean either a second row or a duplicate of this one. | Create the row you want (new X { … }), or change the fields on the row you have. |
| entity 'X' has no property 'Y' | The initializer names a field the class does not declare. | Check the field name against the declaration. |
| 'X.Y' is readonly | A readonly field cannot be set by a copy. | Pass the value to the constructor instead. |
See also#
- Classes — what a class is, and when to reach for one instead of an entity
- Records — the type
withwas made for, compared by its members - Comparing classes — what a copy is equal to
readonlyfields — the one kind of field a copy may not replace- constructor — building a value from scratch rather than from another one
- entity — why a stored row is copied differently