Guidance for using Generator.Equals — a C# source generator for auto-generating Equals, GetHashCode, operators, and Diff/Inequalities methods via attributes.
Installs into .claude/skills of the current project.
Are you the author of generator.equals?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/diegofrata-generator-equals)
---
name: generator.equals
description: Guidance for using Generator.Equals — a C# source generator for auto-generating Equals, GetHashCode, operators, and Diff/Inequalities methods via attributes.
packages: Generator.Equals
---
# Generator.Equals
C# source generator that auto-implements `IEquatable<T>`, `Equals()`, `GetHashCode()`, `==`/`!=` operators, and an `Inequalities()` diff method at compile time using attributes. No runtime reflection.
## Setup
Add both packages:
```xml
<PackageReference Include="Generator.Equals" PrivateAssets="all" />
<PackageReference Include="Generator.Equals.Runtime" />
```
Requires C# 9.0+. The type **must be `partial`**.
## Attributes Quick Reference
### Type-level
| Attribute | Purpose |
|-----------|---------|
| `[Equatable]` | Generates equality members for the type |
| `[Equatable(Explicit = true)]` | Only compare properties with explicit equality attributes |
| `[Equatable(IgnoreInheritedMembers = true)]` | Skip base class members entirely |
| `[Equatable(GenerateClassEqualityOperators = false)]` | Do not generate `==`/`!=` for a class; leave `==` as reference equality |
### Property/Field-level
| Attribute | Use When |
|-----------|----------|
| `[DefaultEquality]` | Default comparer. **Required on fields** (fields are opt-in; properties are compared by default). |
| `[IgnoreEquality]` | Skip this member |
| `[OrderedEquality]` | Compare collection elements in order (like `SequenceEqual`) |
| `[UnorderedEquality]` | Compare collection elements ignoring order |
| `[SetEquality]` | Compare as sets (duplicates ignored) |
| `[ReferenceEquality]` | Compare by reference only |
| `[StringEquality(StringComparison.X)]` | String-specific comparison (string props only) |
| `[PrecisionEquality(0.001)]` | Tolerance-based numeric comparison |
| `[CustomEquality(typeof(MyComparer))]` | Custom `IEqualityComparer<T>` |
### Comparer constructor patterns (for collection and custom attributes)
```csharp
[OrderedEquality] // Default comparer
[OrderedEquality(typeof(MyComparer))] // Type with static Default member
[OrderedEquality(typeof(StringComparer), nameof(StringComparer.Ordinal))] // Named static member
[OrderedEquality(StringComparison.OrdinalIgnoreCase)] // StringComparison shorthand
```
## Best Practices
### Always annotate collection properties
```csharp
// WRONG - produces diagnostic GE001
public List<int> Items { get; set; }
// RIGHT
[OrderedEquality]
public List<int> Items { get; set; }
```
Two exceptions: a collection type that is itself `[Equatable]` already compares structurally, so no
attribute is needed. And `[DefaultEquality]` opts the member into the type's own `Equals` — use it for
collections that implement their own value equality.
```csharp
[DefaultEquality]
public MyImmutableList<int> Items { get; set; } // MyImmutableList compares structurally itself
```
### Use Explicit mode when only a few properties matter
```csharp
[Equatable(Explicit = true)]
partial class User
{
[DefaultEquality] public string Id { get; set; } // Compared
public string DisplayName { get; set; } // Ignored
public DateTime LastLogin { get; set; } // Ignored
}
```
### Mark fields explicitly
Fields are opt-in (since v5): they are **not** compared unless annotated. Properties are compared by
default. To include a field, annotate it with `[DefaultEquality]` (or another equality attribute):
```csharp
[DefaultEquality]
private int _version;
```
### Use the generated EqualityComparer
Every `[Equatable]` type gets a nested `EqualityComparer` class:
```csharp
// Use in dictionaries, HashSets, LINQ
var dict = new Dictionary<MyType, string>(MyType.EqualityComparer.Default);
var distinct = items.Distinct(MyType.EqualityComparer.Default);
```
### Use Inequalities for diff/audit
```csharp
foreach (var diff in MyType.EqualityComparer.Default.Inequalities(oldObj, newObj))
Console.WriteLine(diff);
// Output: Name: John -> Jane
// Output: Addresses["home"].Street: 123 Main St -> 456 Oak Ave
```
Nested `[Equatable]` objects in collections are auto-drilled — you get per-field diffs, not "entire object changed".
## Common Pitfalls
1. **Non-partial type** (GE006) — The type MUST be declared `partial`.
2. **Manual Equals/GetHashCode** (GE005) — Do NOT override `Equals()` or `GetHashCode()` manually on an `[Equatable]` type. The generator owns these.
3. **Conflicting collection attributes** (GE007) — Only ONE of `[OrderedEquality]`, `[UnorderedEquality]`, `[SetEquality]` per property.
4. **Hash code instability** — `[UnorderedEquality]`, `[SetEquality]`, and `[PrecisionEquality]` return hash code 0 or exclude from hashing. Types using these are poor dictionary keys.
5. **Inheritance with [Equatable]** — The generator walks the full inheritance chain. If any ancestor has `[Equatable]` or overrides `Equals()`, it calls `base.Equals()`. Use `IgnoreInheritedMembers = true` to opt out.
6. **Overriding properties inherits attributes** — A `Child` overriding a `Parent`'s `[OrderedEquality] virtual int[] Values` automatically inherits `[OrderedEquality]`. Do NOT re-annotate unless you want to change behavior.
7. **`[StringEquality]` on non-string** (GE008) — Only valid on `string` properties.
8. **`[PrecisionEquality]` types** (GE010) — Only `float`, `double`, `decimal`, `int`, `long`, `short`, `sbyte` and their nullable variants.
9. **`GenerateClassEqualityOperators` is class-only** (GE011) — Records always get compiler-generated operators; structs always get generated ones, since C# predefines `==` for reference types but not for user-defined structs — dropping them would make every `==` on the type a `CS0019` error, with no reference-equality fallback like a class has.
10. **Opting out of operators must cover the whole chain** (GE012) — C# operator overload resolution considers base-type operators, so `GenerateClassEqualityOperators = false` on a derived class alone does nothing. Set it on the root of the `[Equatable]` chain too.
## Examples
### Basic class
```csharp
[Equatable]
partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
[IgnoreEquality]
public DateTime LastUpdated { get; set; }
}
```
### Record with collections
```csharp
[Equatable]
partial record Customer(
string Id,
string Name,
[property: OrderedEquality] ImmutableArray<Address> Addresses,
[property: UnorderedEquality] ImmutableDictionary<string, string> Tags
);
```
### Struct with custom comparer
```csharp
[Equatable]
partial struct Coordinate
{
[PrecisionEquality(0.0001)]
public double Latitude { get; set; }
[PrecisionEquality(0.0001)]
public double Longitude { get; set; }
}
```
### Inheritance
```csharp
[Equatable]
partial class Animal
{
public string Species { get; set; }
}
[Equatable]
partial class Pet : Animal
{
public string Name { get; set; }
// Species is also compared via base.Equals()
}
```
### Diff / Inequalities
```csharp
var diffs = Customer.EqualityComparer.Default.Inequalities(before, after);
foreach (var d in diffs)
{
// d.Path — e.g., "Addresses[0].Street"
// d.Left — old value
// d.Right — new value
Console.WriteLine(d);
}
```
## Diagnostics
| Code | Description |
|------|-------------|
| GE001 | Collection missing equality attribute |
| GE002 | Complex property missing `[Equatable]` |
| GE003 | Collection element missing `[Equatable]` |
| GE004 | Equality attribute used but containing type lacks `[Equatable]` |
| GE005 | Manual Equals/GetHashCode with `[Equatable]` |
| GE006 | `[Equatable]` on non-partial type |
| GE007 | Conflicting equality attributes |
| GE008 | `[StringEquality]` on non-string |
| GE009 | Collection attribute on non-collection |
| GE010 | `[PrecisionEquality]` on unsupported type |
| GE011 | `GenerateClassEqualityOperators` on a record or struct |
| GE012 | `GenerateClassEqualityOperators = false` defeated by a base type's operators |
GE001, GE002, GE003, and GE006 have automatic code fixes. Every diagnostic can be suppressed the usual way (`#pragma warning disable`, `.editorconfig`, or `[SuppressMessage]`).