Skip to content

Commit e48207f

Browse files
diegofrataclaude
andcommitted
docs: document Inequalities feature in README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 882084f commit e48207f

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Writing correct equality logic in C# is tedious, error-prone, and easy to forget
1212
- **Highly customizable** — Use `[CustomEquality]`, `[StringEquality]`, `[PrecisionEquality]`, `[ReferenceEquality]`, or `[IgnoreEquality]` to control comparison per-property.
1313
- **Works everywhere** — Supports classes, structs, records, and record structs.
1414
- **Inheritance-friendly** — Correctly chains `base.Equals()` across deep inheritance hierarchies and inherits equality attributes from overridden properties.
15+
- **Structured diffs** — Call `Inequalities()` to get exactly which members differ between two instances, with full paths into nested objects, collections, and dictionaries.
1516
- **Compile-time only** — No reflection or IL injection. The generator emits plain C# source code that you can inspect and debug.
1617

1718
----------------
@@ -292,6 +293,84 @@ partial class Doctor : Person
292293
}
293294
```
294295

296+
## Inequalities
297+
298+
Every `[Equatable]` type gets a generated `EqualityComparer` with an `Inequalities()` method that returns exactly which members differ between two instances — with full member paths, including nested objects, collection indices, and dictionary keys.
299+
300+
```c#
301+
[Equatable]
302+
partial record Customer
303+
{
304+
public required string Name { get; init; }
305+
[UnorderedEquality] public required ImmutableDictionary<string, Address> Addresses { get; init; }
306+
}
307+
308+
[Equatable]
309+
partial record Address
310+
{
311+
public required string Street { get; init; }
312+
public required string City { get; init; }
313+
}
314+
```
315+
316+
```c#
317+
var original = new Customer
318+
{
319+
Name = "John Doe",
320+
Addresses = ImmutableDictionary<string, Address>.Empty
321+
.Add("home", new Address { Street = "123 Main St", City = "Seattle" })
322+
};
323+
324+
var modified = original with
325+
{
326+
Name = "Johnny Doe",
327+
Addresses = original.Addresses.SetItem("home",
328+
original.Addresses["home"] with { Street = "121 Main St" })
329+
};
330+
331+
foreach (var diff in Customer.EqualityComparer.Default.Inequalities(original, modified))
332+
Console.WriteLine(diff);
333+
```
334+
335+
Output:
336+
```
337+
Name: John Doe → Johnny Doe
338+
Addresses["home"].Street: 123 Main St → 121 Main St
339+
```
340+
341+
### How it works
342+
343+
`Inequalities()` returns `IEnumerable<Inequality>`, where each `Inequality` has:
344+
345+
- **`Path`** — a `MemberPath` describing which member differs (e.g., `Addresses["home"].Street`)
346+
- **`Left`** — the value from the first object
347+
- **`Right`** — the value from the second object
348+
349+
Paths are composed of segments:
350+
351+
| Segment | Example | Meaning |
352+
|---------|---------|---------|
353+
| `Property` | `Name` | A property differs |
354+
| `Field` | `_count` | A field differs |
355+
| `Index` | `[0]` | An ordered collection element at index |
356+
| `Key` | `["home"]` | A dictionary entry by key |
357+
| `Added` | `[+]` | An element present only in the right set |
358+
| `Removed` | `[-]` | An element present only in the left set |
359+
360+
### Drill-down
361+
362+
When a collection element or dictionary value is itself an `[Equatable]` type, `Inequalities()` automatically drills down into its properties instead of reporting the entire object as changed. In the example above, the address change is reported as `Addresses["home"].Street` rather than the whole `Address` object.
363+
364+
### Base path
365+
366+
You can pass a base path to prefix all reported paths — useful when composing inequalities from parent contexts:
367+
368+
```c#
369+
var diffs = Address.EqualityComparer.Default.Inequalities(
370+
addressA, addressB, new MemberPath(new[] { MemberPathSegment.Property("Home") }));
371+
// Reports: Home.Street, Home.City, etc.
372+
```
373+
295374
## Migrating from version 3
296375

297376
### Inherited Equality Attributes

0 commit comments

Comments
 (0)