# Copying a class with changes

> `with` makes a COPY of a class value and replaces the fields you name. Everything you do not name comes from the value you copied, and the original is left untouched — so it is how you derive one value from another without rebuilding it field by field, and without mutating something another part of the program is still holding.

<!-- id: class-with · area: class · stability: preview · html: https://osysharp.com/reference/class/with/ -->

## Summary        {#summary}
`with` copies a class value and replaces the fields you name:

```osy title="a copy with one field changed" test app=class-with
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      {#signature}
```osy syntax
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 before
```

`with` is available on any [class](https://osysharp.com/reference/class/index/). It is not available on an `entity`, or on a scalar — see
[[#errors|Errors]].

## Description    {#description}

### The fields you do not name come from the value you copied   {#copied-fields}
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:

```osy title="declared defaults do not come back" test app=class-with-defaults
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   {#immutable}
`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:

```osy title="the donor is untouched" test app=class-with-donor
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   {#runtime-type}
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:

```osy title="a Dog held as an Animal copies to a Dog" test app=class-with-subtype
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
}
```

### Why there is no `record` keyword   {#no-record}
In C#, `with` works on a `record` and not on a plain `class`, because the two differ in how they compare. In Osy#
they do not: a class already compares [by value](https://osysharp.com/reference/class/equality/), which is what a C# `record` is. A second keyword
would therefore separate nothing, so `with` is simply available on every class.

A consequence worth knowing: a plain copy **equals** the value it came from.

```osy title="a copy with nothing replaced is equal" test app=class-with-equality
class Point { public decimal X; public decimal Y; }

bool SameValue() {
  var p = new Point { X = 1m, Y = 2m };
  return p == p with { };        // true — same class, same fields
}
```

### `readonly` fields still cannot be set   {#readonly}
A [`readonly`](https://osysharp.com/reference/class/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       {#examples}

```osy title="deriving a series of values from one" test app=class-with-series
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         {#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. |
| ``with` copies an in-memory `class`, 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       {#see-also}
- [Classes](https://osysharp.com/reference/class/index/) — what a class is, and when to reach for one instead of an entity
- [Comparing classes](https://osysharp.com/reference/class/equality/) — why a class compares by value, which is why `with` needs no `record` keyword
- [`readonly` fields](https://osysharp.com/reference/class/readonly/) — the one kind of field a copy may not replace
- [constructor](https://osysharp.com/reference/class/constructors/) — building a value from scratch rather than from another one
- [entity](https://osysharp.com/reference/entity/declaration/) — why a stored row is copied differently
