Equatable.Generator
3.0.0-beta.2
See the version list below for details.
dotnet add package Equatable.Generator --version 3.0.0-beta.2
NuGet\Install-Package Equatable.Generator -Version 3.0.0-beta.2
<PackageReference Include="Equatable.Generator" Version="3.0.0-beta.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Equatable.Generator" Version="3.0.0-beta.2" />
<PackageReference Include="Equatable.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Equatable.Generator --version 3.0.0-beta.2
#r "nuget: Equatable.Generator, 3.0.0-beta.2"
#:package Equatable.Generator@3.0.0-beta.2
#addin nuget:?package=Equatable.Generator&version=3.0.0-beta.2&prerelease
#tool nuget:?package=Equatable.Generator&version=3.0.0-beta.2&prerelease
Equatable.Generator
A C# source generator that writes Equals, GetHashCode, and equality operators for you, with attribute based control over how each member is compared.
Features
- Generates
Equals(object),Equals(T), andGetHashCode()overrides - Implements
IEquatable<T>and the==/!=operators - Supports
class,record, andstructtypes - Per member comparer selection through attributes
- Built-in comparers: string, sequence, dictionary, set, reference, and custom
- Works on properties and fields
- No runtime dependencies; the package is compile time only
Installation
dotnet add package Equatable.Generator
The generator emits its own attributes, so nothing needs to flow to consumers of your library. Mark the reference as private to keep it out of your package dependencies:
<PackageReference Include="Equatable.Generator" PrivateAssets="all" />
Requirements
- Roslyn 4.14 or later. In practice: Visual Studio 2022 17.14+, Visual Studio 2026+, a current Rider release, or the .NET SDK 9.0.300 or newer.
- Project C#
LangVersion8.0 or higher
Quick start
Mark a partial type with [Equatable]. The generator creates the matching partial with equality members for all public properties and fields.
using Equatable.Attributes;
[Equatable]
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; } = null!;
}
var first = new Product { Id = 1, Name = "Widget" };
var second = new Product { Id = 1, Name = "Widget" };
Console.WriteLine(first == second); // True
Console.WriteLine(first.Equals(second)); // True
Console.WriteLine(first is IEquatable<Product>); // True
For class and struct types the generator emits IEquatable<T>, both Equals methods, GetHashCode, and the == / != operators. For record types it emits the strongly typed Equals and GetHashCode only, because the compiler supplies the rest.
Default comparison rules
| Member type | Default behavior |
|---|---|
| Value types | The == operator, avoiding boxing and comparer lookups |
string types |
StringComparer.Ordinal (same as string.Equals) |
| Everything else | EqualityComparer<T>.Default |
Use the attributes below to change the behavior of an individual member.
Attributes
All attributes live in the Equatable.Attributes namespace.
| Attribute | Applies to | Behavior |
|---|---|---|
[Equatable] |
class, record, struct | Generates the equality members for the type |
[IgnoreEquality] |
property, field | Excludes the member from Equals and GetHashCode |
[StringEquality(StringComparison)] |
property, field | Uses the StringComparer matching the supplied StringComparison |
[SequenceEquality] |
property, field | Compares elements in order with Enumerable.SequenceEqual; order affects the hash code |
[DictionaryEquality] |
property, field | Compares entry counts and per key values; order independent |
[HashSetEquality] |
property, field | Compares contents with ISet<T>.SetEquals; order independent |
[ReferenceEquality] |
property, field | Compares with Object.ReferenceEquals and hashes with RuntimeHelpers.GetHashCode |
[EqualityComparer(Type, Property)] |
property, field | Uses the comparer returned by the named static property on the given type |
Custom comparer example
[Equatable]
public partial class Document
{
[EqualityComparer(typeof(TrimmedComparer), nameof(TrimmedComparer.Instance))]
public string? Title { get; set; }
}
public sealed class TrimmedComparer : IEqualityComparer<string?>
{
public static TrimmedComparer Instance { get; } = new();
public bool Equals(string? x, string? y)
=> string.Equals(x?.Trim(), y?.Trim(), StringComparison.Ordinal);
public int GetHashCode(string? obj)
=> obj?.Trim().GetHashCode() ?? 0;
}
Full example
[Equatable]
public partial class UserImport
{
[StringEquality(StringComparison.OrdinalIgnoreCase)]
public string EmailAddress { get; set; } = null!;
public string? DisplayName { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTimeOffset? LockoutEnd { get; set; }
public DateTimeOffset? LastLogin { get; set; }
[IgnoreEquality]
public string FullName => $"{FirstName} {LastName}";
[HashSetEquality]
public HashSet<string>? Roles { get; set; }
[DictionaryEquality]
public Dictionary<string, int>? Permissions { get; set; }
[SequenceEquality]
public List<DateTimeOffset>? History { get; set; }
}
Records, including positional records, are supported. Use the property: target so the attribute lands on the generated property:
[Equatable]
public partial record StatusRecord(
int Id,
[property: StringEquality(StringComparison.OrdinalIgnoreCase)] string Name,
string? Description,
int DisplayOrder,
bool IsActive,
[property: SequenceEquality] List<string> Versions
);
Structs work the same way:
[Equatable]
public partial struct Coordinate
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
Notes and limitations
- The type must be declared
partial. - Nested types are supported as long as every containing type is also
partial. - The attributes are conditional on the
EQUATABLE_GENERATORsymbol, so they leave no trace in the compiled output.
Contributing
Issues and pull requests are welcome on GitHub.
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 0 | 8/4/2026 |
| 3.0.0-beta.2 | 0 | 8/3/2026 |
| 3.0.0-beta.1 | 22 | 8/3/2026 |
| 2.2.1 | 11,916 | 4/10/2026 |
| 2.2.0 | 231 | 4/8/2026 |
| 2.1.0 | 12,516 | 10/22/2025 |
| 2.0.0 | 6,224 | 11/14/2024 |
| 1.1.0 | 602 | 9/7/2024 |
| 1.1.0-beta.1 | 177 | 9/7/2024 |
| 1.0.0 | 330 | 9/5/2024 |
| 1.0.0-beta.3 | 142 | 9/5/2024 |
| 1.0.0-beta.2 | 143 | 9/5/2024 |
| 1.0.0-beta.1 | 171 | 9/5/2024 |
| 1.0.0-alpha.2 | 170 | 9/4/2024 |
| 1.0.0-alpha.1 | 164 | 9/4/2024 |