You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
4
16
5
17
----------------
6
18
## Requirements
@@ -13,97 +25,6 @@ In order to use this library, you must:
13
25
14
26
Simply add the package `Generator.Equals` to your project. Keep reading to learn how to add the attributes to your types.
15
27
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
-
publicpartialclassParent
28
-
{
29
-
[OrderedEquality]
30
-
publicvirtualint[] Values { get; set; }
31
-
}
32
-
33
-
[Equatable]
34
-
publicpartialclassChild : Parent
35
-
{
36
-
// Automatically inherits [OrderedEquality] from Parent
37
-
publicoverrideint[] 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
-
publicpartialclassGrandParent
55
-
{
56
-
publicstringName { get; set; }
57
-
}
58
-
59
-
// No [Equatable] - inherits GrandParent's Equals
60
-
publicclassParent : GrandParent
61
-
{
62
-
publicintAge { get; set; }
63
-
}
64
-
65
-
[Equatable]
66
-
publicpartialclassChild : Parent
67
-
{
68
-
publicstringSchool { 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 |
|`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
-
publicpartialclassChild : Parent
91
-
{
92
-
// Will NOT call base.Equals() even if Parent has [Equatable]
93
-
// Only properties defined in Child are compared
94
-
publicstringSchool { 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
-
107
28
## Usage
108
29
109
30
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
353
274
publicstringSpecialization { get; set; }
354
275
}
355
276
```
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
+
publicpartialclassParent
290
+
{
291
+
[OrderedEquality]
292
+
publicvirtualint[] Values { get; set; }
293
+
}
294
+
295
+
[Equatable]
296
+
publicpartialclassChild : Parent
297
+
{
298
+
// Automatically inherits [OrderedEquality] from Parent
299
+
publicoverrideint[] 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
+
publicpartialclassGrandParent
317
+
{
318
+
publicstringName { get; set; }
319
+
}
320
+
321
+
// No [Equatable] - inherits GrandParent's Equals
322
+
publicclassParent : GrandParent
323
+
{
324
+
publicintAge { get; set; }
325
+
}
326
+
327
+
[Equatable]
328
+
publicpartialclassChild : Parent
329
+
{
330
+
publicstringSchool { 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 |
|`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
+
publicpartialclassChild : Parent
353
+
{
354
+
// Will NOT call base.Equals() even if Parent has [Equatable]
355
+
// Only properties defined in Child are compared
356
+
publicstringSchool { 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