بنقرة واحدة
generator-equals
// Guidance for using Generator.Equals — a C# source generator for auto-generating Equals, GetHashCode, operators, and Diff/Inequalities methods via attributes.
// Guidance for using Generator.Equals — a C# source generator for auto-generating Equals, GetHashCode, operators, and Diff/Inequalities methods via attributes.
| 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 |
C# source generator that auto-implements IEquatable<T>, Equals(), GetHashCode(), ==/!= operators, and an Inequalities() diff method at compile time using attributes. No runtime reflection.
Add both packages:
<PackageReference Include="Generator.Equals" PrivateAssets="all" />
<PackageReference Include="Generator.Equals.Runtime" />
Requires C# 9.0+. The type must be partial.
| 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 |
| 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> |
[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
// WRONG - produces diagnostic GE001
public List<int> Items { get; set; }
// RIGHT
[OrderedEquality]
public List<int> Items { get; set; }
[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
}
Fields are not included by default. You must annotate them:
[DefaultEquality]
private int _version;
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);
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".
Non-partial type (GE006) — The type MUST be declared partial.
Manual Equals/GetHashCode (GE005) — Do NOT override Equals() or GetHashCode() 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 overrides Equals(), it calls base.Equals(). Use IgnoreInheritedMembers = true to opt out.
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.
[StringEquality] on non-string (GE008) — Only valid on string properties.
[PrecisionEquality] types (GE010) — Only float, double, decimal, int, long, short, sbyte and their nullable variants.
[Equatable]
partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
[IgnoreEquality]
public DateTime LastUpdated { get; set; }
}
[Equatable]
partial record Customer(
string Id,
string Name,
[property: OrderedEquality] ImmutableArray<Address> Addresses,
[property: UnorderedEquality] ImmutableDictionary<string, string> Tags
);
[Equatable]
partial struct Coordinate
{
[PrecisionEquality(0.0001)]
public double Latitude { get; set; }
[PrecisionEquality(0.0001)]
public double Longitude { get; set; }
}
[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()
}
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);
}
| 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.