Skip to content

Commit 285b2b7

Browse files
committed
chore: improve readme
1 parent a44852f commit 285b2b7

1 file changed

Lines changed: 104 additions & 92 deletions

File tree

README.md

Lines changed: 104 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
[![Nuget](https://img.shields.io/nuget/v/Generator.Equals)](https://www.nuget.org/packages/Generator.Equals/)
22
# Generator.Equals
3-
A source code generator for automatically implementing IEquatable<T> using only attributes.
3+
4+
**A C# source generator that automatically implements `IEquatable<T>`, `Equals`, and `GetHashCode` — using just attributes.**
5+
6+
Writing correct equality logic in C# is tedious, error-prone, and easy to forget when adding new properties. Generator.Equals eliminates that boilerplate by generating efficient, best-practice equality code at compile time.
7+
8+
### Why use Generator.Equals?
9+
10+
- **Zero boilerplate** — Mark your type with `[Equatable]` and the generator does the rest. No hand-written `Equals`, `GetHashCode`, or `==`/`!=` operators.
11+
- **Collection-aware** — Compare arrays, lists, dictionaries, and sets by value out of the box with `[OrderedEquality]`, `[UnorderedEquality]`, and `[SetEquality]`.
12+
- **Highly customizable** — Use `[CustomEquality]`, `[StringEquality]`, `[ReferenceEquality]`, or `[IgnoreEquality]` to control comparison per-property.
13+
- **Works everywhere** — Supports classes, structs, records, and record structs.
14+
- **Inheritance-friendly** — Correctly chains `base.Equals()` across deep inheritance hierarchies and inherits equality attributes from overridden properties.
15+
- **Compile-time only** — No runtime dependencies. The generator emits plain C# source code with no reflection or allocations.
416

517
----------------
618
## Requirements
@@ -13,97 +25,6 @@ In order to use this library, you must:
1325

1426
Simply add the package `Generator.Equals` to your project. Keep reading to learn how to add the attributes to your types.
1527

16-
## Migrating from version 3
17-
18-
### Inherited Equality Attributes
19-
20-
Version 4 introduces support for inherited equality attributes on overridden properties, making repeating attributes
21-
unnecessary. When a child class overrides a virtual property from a parent class, it now automatically inherits the
22-
equality attribute (e.g., `[OrderedEquality]`) from the parent. You no longer need to redeclare attributes on overriding
23-
properties.
24-
25-
```c#
26-
[Equatable]
27-
public partial class Parent
28-
{
29-
[OrderedEquality]
30-
public virtual int[] Values { get; set; }
31-
}
32-
33-
[Equatable]
34-
public partial class Child : Parent
35-
{
36-
// Automatically inherits [OrderedEquality] from Parent
37-
public override int[] Values { get; set; }
38-
}
39-
```
40-
41-
### Improved Inheritance Chain Detection
42-
43-
Version 4 improves how `base.Equals()` is called in inheritance hierarchies. Previously, generated code would only
44-
call `base.Equals()` if the **immediate** base class had `[Equatable]`. Now, the generator walks the **entire**
45-
inheritance chain and calls `base.Equals()` if:
46-
47-
1. **Any ancestor** has the `[Equatable]` attribute, OR
48-
2. **Any ancestor** has manually overridden `Equals(object)`
49-
50-
This fixes scenarios where equality was incorrectly skipped in multi-level inheritance:
51-
52-
```c#
53-
[Equatable]
54-
public partial class GrandParent
55-
{
56-
public string Name { get; set; }
57-
}
58-
59-
// No [Equatable] - inherits GrandParent's Equals
60-
public class Parent : GrandParent
61-
{
62-
public int Age { get; set; }
63-
}
64-
65-
[Equatable]
66-
public partial class Child : Parent
67-
{
68-
public string School { get; set; }
69-
}
70-
```
71-
72-
**Before (v3):** `Child.Equals()` did NOT call `base.Equals()` because `Parent` lacks `[Equatable]`.
73-
Only `School` was compared, ignoring `Name`.
74-
75-
**After (v4):** `Child.Equals()` calls `base.Equals()` because `GrandParent` has `[Equatable]`.
76-
Both `School` and `Name` are compared correctly.
77-
78-
### Ignore Inherited Members
79-
80-
The `IgnoreInheritedMembers` property controls how inherited members are handled:
81-
82-
| IgnoreInheritedMembers | Any Ancestor has [Equatable] | Behavior |
83-
|------------------------|------------------------------|----------|
84-
| `true` | N/A | Compare only declared members, type check, no `base.Equals()` |
85-
| `false` | Yes | Call `base.Equals()`, let ancestor handle its members |
86-
| `false` | No | Type check + compare ALL inherited properties from entire chain |
87-
88-
```c#
89-
[Equatable(IgnoreInheritedMembers = true)]
90-
public partial class Child : Parent
91-
{
92-
// Will NOT call base.Equals() even if Parent has [Equatable]
93-
// Only properties defined in Child are compared
94-
public string School { get; set; }
95-
}
96-
```
97-
98-
## Migrating from version 2
99-
100-
Migrating to version 3 is very straightforward.
101-
102-
1. Ensure projects are targeting C# 9.0 or latter using the MSBuild property `LangVersion`.
103-
2. Be aware that `IEquatable<T>` for classes is now implemented explicitly in order to support deep equality. As a result, the method `Equals(T)` method is no longer marked as public. Most code should still work, requiring only to be recompiled as the ABI has changed.
104-
105-
If you have an existing project using `Generator.Equals` and don't need any of the new features, you can still use version 2.x. The differences are minimal between both major versions.
106-
10728
## Usage
10829

10930
The below sample shows how to use Generator.Equals to override the default equality implementation for a C# record, enhancing it with the ability to determine the equality between the array contents of the record.
@@ -353,3 +274,94 @@ partial class Doctor : Person
353274
public string Specialization { get; set; }
354275
}
355276
```
277+
278+
## Migrating from version 3
279+
280+
### Inherited Equality Attributes
281+
282+
Version 4 introduces support for inherited equality attributes on overridden properties, making repeating attributes
283+
unnecessary. When a child class overrides a virtual property from a parent class, it now automatically inherits the
284+
equality attribute (e.g., `[OrderedEquality]`) from the parent. You no longer need to redeclare attributes on overriding
285+
properties.
286+
287+
```c#
288+
[Equatable]
289+
public partial class Parent
290+
{
291+
[OrderedEquality]
292+
public virtual int[] Values { get; set; }
293+
}
294+
295+
[Equatable]
296+
public partial class Child : Parent
297+
{
298+
// Automatically inherits [OrderedEquality] from Parent
299+
public override int[] Values { get; set; }
300+
}
301+
```
302+
303+
### Improved Inheritance Chain Detection
304+
305+
Version 4 improves how `base.Equals()` is called in inheritance hierarchies. Previously, generated code would only
306+
call `base.Equals()` if the **immediate** base class had `[Equatable]`. Now, the generator walks the **entire**
307+
inheritance chain and calls `base.Equals()` if:
308+
309+
1. **Any ancestor** has the `[Equatable]` attribute, OR
310+
2. **Any ancestor** has manually overridden `Equals(object)`
311+
312+
This fixes scenarios where equality was incorrectly skipped in multi-level inheritance:
313+
314+
```c#
315+
[Equatable]
316+
public partial class GrandParent
317+
{
318+
public string Name { get; set; }
319+
}
320+
321+
// No [Equatable] - inherits GrandParent's Equals
322+
public class Parent : GrandParent
323+
{
324+
public int Age { get; set; }
325+
}
326+
327+
[Equatable]
328+
public partial class Child : Parent
329+
{
330+
public string School { get; set; }
331+
}
332+
```
333+
334+
**Before (v3):** `Child.Equals()` did NOT call `base.Equals()` because `Parent` lacks `[Equatable]`.
335+
Only `School` was compared, ignoring `Name`.
336+
337+
**After (v4):** `Child.Equals()` calls `base.Equals()` because `GrandParent` has `[Equatable]`.
338+
Both `School` and `Name` are compared correctly.
339+
340+
### Ignore Inherited Members
341+
342+
The `IgnoreInheritedMembers` property controls how inherited members are handled:
343+
344+
| IgnoreInheritedMembers | Any Ancestor has [Equatable] | Behavior |
345+
|------------------------|------------------------------|----------|
346+
| `true` | N/A | Compare only declared members, type check, no `base.Equals()` |
347+
| `false` | Yes | Call `base.Equals()`, let ancestor handle its members |
348+
| `false` | No | Type check + compare ALL inherited properties from entire chain |
349+
350+
```c#
351+
[Equatable(IgnoreInheritedMembers = true)]
352+
public partial class Child : Parent
353+
{
354+
// Will NOT call base.Equals() even if Parent has [Equatable]
355+
// Only properties defined in Child are compared
356+
public string School { get; set; }
357+
}
358+
```
359+
360+
## Migrating from version 2
361+
362+
Migrating to version 3 is very straightforward.
363+
364+
1. Ensure projects are targeting C# 9.0 or latter using the MSBuild property `LangVersion`.
365+
2. Be aware that `IEquatable<T>` for classes is now implemented explicitly in order to support deep equality. As a result, the method `Equals(T)` method is no longer marked as public. Most code should still work, requiring only to be recompiled as the ABI has changed.
366+
367+
If you have an existing project using `Generator.Equals` and don't need any of the new features, you can still use version 2.x. The differences are minimal between both major versions.

0 commit comments

Comments
 (0)