# Comparing classes

> `==` on two `class` values asks whether they are the SAME INSTANCE, exactly as C# does: two objects built separately are two objects, however equal their fields. A `record` (and a `record struct`) compares by its members instead, and so does `Equals` on a `struct`. The answer is the same on the server and in the browser, and an instance handed to a server function and back is still the same instance.

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

## Summary        {#summary}
A `class` compares by **identity**; a `record` by its **members**:

```osy title="two instances, and two equal records" test app=class-equality
class Money { public decimal Amount; public string Currency = ""; }
record Price(decimal Amount, string Currency);

string Compare() {
  var a = new Money { Amount = 9.50m, Currency = "USD" };
  var b = new Money { Amount = 9.50m, Currency = "USD" };
  var p = new Price(9.50m, "USD");
  var q = new Price(9.50m, "USD");
  return $"{a == b} {a == a} {p == q}";   // "False True True"
}
```

`a` and `b` hold the same fields and are still two instances. `p` and `q` are records, so they are equal because
every member is.

## Signature      {#signature}
```osy syntax
a == b          // class: the same instance · record / record struct: every member equal
a != b          // the negation
a.Equals(b)     // the same question — and the only one a plain struct answers (it has no ==)
a == null       // a class or a record is a reference type, so this is a legitimate check
```

## Description    {#description}

### Why identity   {#why}
It is C#'s answer, and a class is how C# spells "a thing with an identity": the order being edited, the column the
grid is sorted by, the step a wizard is on. Two of them with the same fields are still two of them.

The runtime keeps that answer honest where it could otherwise slip. A class value that crosses from the browser to
the server and back — an argument to a function that runs on the server and returns it — is rebuilt on each side, so each
instance carries a hidden identity that travels with it, and the rebuilt object is the instance it was made from:
`Echo(m) == m` is `true`, as in C#, and a twin built with the same fields is not.

What it does mean: a `new` inside a component's `render` makes a new instance every render, exactly as a `new`
inside a C# method makes one every call. When you need two separately built values to compare equal — a sort column
rebuilt each render, a filter in a list — make the type a [`record`](https://osysharp.com/reference/class/records/).

### What a record compares   {#field-comparison}
A record (and a `record struct`, and `Equals` on a [struct](https://osysharp.com/reference/class/structs/)) compares every member with `==`, so the
rule composes:

| a member holding | compares by |
|---|---|
| a scalar (`int`, `string`, `decimal`, `DateTime`, an enum) | its value |
| a record or a struct | its members, recursively |
| a plain class | its identity |
| a list or an array (`string[]`, `List<T>`) | its elements, in order |
| an entity | that entity's identity, exactly as `==` on an entity does |
| a `Func<…>` / `Action<…>` | the lambda it came from — see below |
| nothing (never assigned) | `null`, which is what an unset member is |

Two records of **different** types are never equal, however identical their members. `Distinct()`, `Contains`,
`IndexOf` and a dictionary key follow the same rule as `==`: two equal records are one element and one key, and two
instances of a plain class are two.

### A member holding a function   {#function-fields}
A selector member compares by **which lambda it is**, not by the object wrapping it:

```osy title="a record carrying a selector" test app=class-equality-func
record Column<T> {
  public string Label = "";
  public Func<T, string> Value;
}
```

Two `Column` records written from the same `r => r.Title` are equal; two written from different lambdas are not —
C#'s "same method" question. That is what lets a shape that carries behaviour, such as a table column, be found
again on the next render.

### Entities compare by identity too   {#entities}
An [entity](https://osysharp.com/reference/entity/declaration/) is a row, so two entity values are equal when they are **the same row** — its id,
not its current field values. [Comparing entity rows](https://osysharp.com/reference/entity/equality/) has that rule in full.

## Examples       {#examples}

```osy title="a rebuilt record still matches the one you kept" test app=class-equality-render
record Column<T> {
  public string Name = "";
  public Func<T, string> Value;
}

[Composable] component SortHeader<T>(Column<T>[] columns) {
  Column<T> sortBy = columns[0];
  bool descending = false;

  // `columns` is rebuilt every render, so `c` is a different INSTANCE from `sortBy` — and, being a record, an equal
  // VALUE. Clicking the sorted column toggles direction; clicking another one selects it.
  action Choose(Column<T> c) {
    if (sortBy == c) { descending = !descending; } else { sortBy = c; descending = false; }
  }

  render {
    Row {
      foreach (var c in columns) {
        Pressable(onClick: () => Choose(c)) { Text(c.Name); }
      }
    }
  }
}
```

## Errors         {#errors}

| Message | Cause | Fix |
|---|---|---|
| `cannot compare 'X' with 'Y'` | The two operands are unrelated types. | Compare values of the same type, or compare a member of each. |
| `` `==` is not defined on the struct 'X' `` | A plain `struct` has no `==` in C#. | Write `a.Equals(b)`, or declare it `record struct X`. |

## See also       {#see-also}
- [Records](https://osysharp.com/reference/class/records/) — a type that compares by its members, with `with`, deconstruction and a readable `ToString`
- [Structs](https://osysharp.com/reference/class/structs/) — a value type: copied where it is stored
- [Classes](https://osysharp.com/reference/class/index/) — what a class is, and when to reach for one instead of an entity
- [Copying a class with changes](https://osysharp.com/reference/class/with/) — copying a value with some members replaced
- [Comparing entity rows](https://osysharp.com/reference/entity/equality/) — why an entity compares by its row
