generator.equals
A source generator that creates Equals, GetHashCode, and equality operators for C# classes and structs.
Install
mkdir -p .claude/skills/generator-equals && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/10956" && unzip -o skill.zip -d .claude/skills/generator-equals && rm skill.zipInstalls to .claude/skills/generator-equals
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Guidance for using Generator.Equals — a C# source generator for auto-generating Equals, GetHashCode, operators, and Diff/Inequalities methods via attributes.Key capabilities
- →Auto-generates Equals and GetHashCode
- →Implements IEquatable and equality operators
- →Generates diff and inequality methods
- →Supports collection and precision-based equality
How it works
It uses C# source generators to implement equality logic at compile time, avoiding runtime reflection.
Inputs & outputs
When to use generator.equals
- →Implementing value-based equality for DTOs
- →Adding precision-based numeric comparisons
- →Configuring collection equality behavior
About this skill
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:
<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 |
Property/Field-level
| Attribute | Use When |
|---|---|
[DefaultEquality] | Default comparer. Required on fields (fields are excluded 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)
[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
// 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.
[DefaultEquality]
public MyImmutableList<int> Items { get; set; } // MyImmutableList compares structurally itself
Use Explicit mode when only a few properties matter
[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 not included by default. You must annotate them:
[DefaultEquality]
private int _version;
Use the generated EqualityComparer
Every [Equatable] type gets a nested EqualityComparer class:
// 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
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
-
Non-partial type (GE006) — The type MUST be declared
partial. -
Manual Equals/GetHashCode (GE005) — Do NOT override
Equals()orGetHashCode()manually on an[Equatable]type. The generator owns these. -
Conflicting collection attributes (GE007) — Only ONE of
[OrderedEquality],[UnorderedEquality],[SetEquality]per property. -
Hash code instability —
[UnorderedEquality],[SetEquality], and[PrecisionEquality]return hash code 0 or exclude from hashing. Types using these are poor dictionary keys. -
Inheritance with [Equatable] — The generator walks the full inheritance chain. If any ancestor has
[Equatable]or overridesEquals(), it callsbase.Equals(). UseIgnoreInheritedMembers = trueto opt out. -
Overriding properties inherits attributes — A
Childoverriding aParent's[OrderedEquality] virtual int[] Valuesautomatically inherits[OrderedEquality]. Do NOT re-annotate unless you want to change behavior. -
[StringEquality]on non-string (GE008) — Only valid onstringproperties. -
[PrecisionEquality]types (GE010) — Onlyfloat,double,decimal,int,long,short,sbyteand their nullable variants.
Examples
Basic class
[Equatable]
partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
[IgnoreEquality]
public DateTime LastUpdated { get; set; }
}
Record with collections
[Equatable]
partial record Customer(
string Id,
string Name,
[property: OrderedEquality] ImmutableArray<Address> Addresses,
[property: UnorderedEquality] ImmutableDictionary<string, string> Tags
);
Struct with custom comparer
[Equatable]
partial struct Coordinate
{
[PrecisionEquality(0.0001)]
public double Latitude { get; set; }
[PrecisionEquality(0.0001)]
public double Longitude { get; set; }
}
Inheritance
[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
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] |
| 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 |
All diagnostics have automatic code fixes.
When not to use it
- →When the type cannot be declared partial
- →When manual Equals/GetHashCode overrides are required
Prerequisites
Limitations
- →Type must be partial
- →Conflicting collection attributes are not allowed
How it compares
It provides high-performance, compile-time equality generation instead of manual or reflection-based implementations.
Compared to similar skills
generator.equals side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| generator.equals (this skill) | 0 | 4mo | No flags | Intermediate |
| csharp-developer | 43 | 2mo | No flags | Advanced |
| csharp-pro | 9 | 4mo | No flags | Intermediate |
| update-roslyn-version | 2 | 2mo | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
csharp-developer
zenobi-us
Expert C# developer specializing in modern .NET development, ASP.NET Core, and cloud-native applications. Masters C# 12 features, Blazor, and cross-platform development with emphasis on performance and clean architecture.
csharp-pro
sickn33
Write modern C# code with advanced features like records, pattern matching, and async/await. Optimizes .NET applications, implements enterprise patterns, and ensures comprehensive testing. Use PROACTIVELY for C# refactoring, performance optimization, or complex .NET solutions.
update-roslyn-version
dotnet
Guide for updating the Roslyn language server version in the vscode-csharp repository. Use this when asked to update Roslyn, bump the Roslyn version, or upgrade the language server version.
orchardcore-module-creator
OrchardCMS
Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality.
coding-standards
dotnet
>-
complexity
managedcode
Use free built-in .NET maintainability analyzers and code metrics configuration to find overly complex methods and coupled code. USE FOR: the team wants to find overly complex methods; cyclomatic complexity thresholds are needed in CI; maintainability metrics or coupling thresholds need to be config