DeepEqual.Generator 1.0.3

dotnet add package DeepEqual.Generator --version 1.0.3
                    
NuGet\Install-Package DeepEqual.Generator -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DeepEqual.Generator" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DeepEqual.Generator" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="DeepEqual.Generator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DeepEqual.Generator --version 1.0.3
                    
#r "nuget: DeepEqual.Generator, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DeepEqual.Generator@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DeepEqual.Generator&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=DeepEqual.Generator&version=1.0.3
                    
Install as a Cake Tool

DeepEqual.Generator

A C# source generator that creates super-fast, allocation-free deep equality comparers for your classes and structs.

Stop writing Equals by hand. Stop serializing to JSON just to compare objects. Just add an attribute, and you get a complete deep comparer generated at compile time.


✨ Why use this?

  • Simple – annotate your models, and you’re done.
  • Flexible – opt-in options for unordered collections, numeric tolerances, string case sensitivity, custom comparers.
  • Robust – covers tricky cases (cycles, sets, dictionaries, polymorphism) that manual code often misses.

⚡ Why is it faster than handwritten code?

  • Compile-time codegen: emitted at build time as optimized IL — no reflection, no runtime expression building.
  • Direct member access: expands equality checks into straight-line code instead of generic loops or helper calls.
  • No allocations: avoids closures, iterators, or boxing that sneak into LINQ or naive implementations.

Result: consistently 5–7× faster than handwritten comparers, and orders of magnitude faster than JSON/library approaches.


🛡️ Why is it more robust?

  • Covers corner cases: nested collections, dictionaries, sets, polymorphism, reference cycles.
  • Deterministic: guarantees the same behavior regardless of field order or shape.
  • Safer than manual: no risk of forgetting a property or comparing the wrong shape.

In short: you get the speed of hand-tuned code, but with the coverage of a well-tested library — and without runtime overhead.


📦 Installation

You need two packages:

dotnet add package DeepEqual.Generator.Shared
dotnet add package DeepEqual.Generator
  • Shared → contains runtime comparers and attributes.
  • Generator → analyzer that emits the equality code at compile time.

If you install only the generator, builds will fail because the generated code depends on the runtime package.


🚀 Quick start

Annotate your type:

using DeepEqual.Generator.Shared;

[DeepComparable]
public sealed class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
}

At compile time, a static helper is generated:

PersonDeepEqual.AreDeepEqual(personA, personB);

🔍 Supported comparisons

  • Primitives & enums – by value.
  • Strings – configurable (ordinal, ignore case, culture aware).
  • DateTime / DateTimeOffset – strict (both Kind/Offset and Ticks must match).
  • Guid, TimeSpan, DateOnly, TimeOnly – by value.
  • Nullable<T> – compared only if both have a value.
  • Arrays & collections – element by element.
  • Dictionaries – key/value pairs deeply compared.
  • Jagged & multidimensional arrays – handled correctly.
  • Object properties – compared polymorphically if the runtime type has a generated helper.
  • Dynamics / ExpandoObject – compared as dictionaries.
  • Cycles – supported (can be turned off if you know your graph has no cycles).

🎛 Options

On the root type

[DeepComparable(OrderInsensitiveCollections = true, IncludeInternals = true, IncludeBaseMembers = true)]
public sealed class Order { … }

Defaults:

  • OrderInsensitiveCollectionsfalse
  • IncludeInternalsfalse
  • IncludeBaseMemberstrue
  • CycleTrackingtrue

On individual members

public sealed class Person
{
    [DeepCompare(Kind = CompareKind.Shallow)]
    public Address? Home { get; set; }

    [DeepCompare(OrderInsensitive = true)]
    public List<string>? Tags { get; set; }

    [DeepCompare(IgnoreMembers = new[] { "CreatedAt", "UpdatedAt" })]
    public AuditInfo Info { get; set; } = new();
}

📚 Ordered vs unordered collections

By default, collections are compared in order. If you want them compared ignoring order (like sets), you can:

  • Enable globally:
[DeepComparable(OrderInsensitiveCollections = true)]
public sealed class OrderBatch
{
    public List<int> Ids { get; set; } = new();
}
  • On specific members:
public sealed class TagSet
{
    [DeepCompare(OrderInsensitive = true)]
    public List<string> Tags { get; set; } = new();
}
  • Or use key-based matching:
[DeepCompare(KeyMembers = new[] { "Id" })]
public sealed class Customer
{
    public string Id { get; set; } = "";
    public string Name { get; set; } = "";
}

⚡ Numeric & string options

var opts = new ComparisonOptions
{
    FloatEpsilon = 0f,
    DoubleEpsilon = 0d,
    DecimalEpsilon = 0m,
    TreatNaNEqual = false,
    StringComparison = StringComparison.Ordinal
};

Defaults: strict equality for numbers and case-sensitive ordinal for strings.


🌀 Cycles

Cyclic graphs are handled safely:

[DeepComparable]
public sealed class Node
{
    public string Id { get; set; } = "";
    public Node? Next { get; set; }
}

var a = new Node { Id = "a" };
var b = new Node { Id = "a" };
a.Next = a;
b.Next = b;

NodeDeepEqual.AreDeepEqual(a, b);

📊 Benchmarks

The generated comparer outperforms handwritten, JSON, and popular libraries by a wide margin:

Method Equal Allocations
Generated 0.3 µs 120 B
Handwritten (Linq) 2.1 µs 3.5 KB
JSON (STJ) 1.401 s 1.4 MB
Compare-Net-Objects 2.099 s 3.4 MB
ObjectsComparer 13.527 s 13 MB
FluentAssertions 10.818 s 21 MB

✅ When to use

  • Large object graphs (domain models, caches, trees).
  • Unit/integration tests where you assert deep equality.
  • Regression testing with snapshot objects.
  • High-throughput APIs needing object deduplication.
  • Anywhere you need correctness and speed.

📦 Roadmap

  • Strict time semantics
  • Numeric tolerances
  • String comparison options
  • Cycle tracking
  • Include internals & base members
  • Order-insensitive collections
  • Key-based unordered matching
  • Custom comparers
  • Memory<T> / ReadOnlyMemory<T>
  • Benchmarks & tests
  • Analyzer diagnostics
  • Developer guide & samples site

update the benchmark to use the unit next to the number and increase unit ns, ms, s, etc, based on the number size

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

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
1.0.3 237 9/15/2025
1.0.2 233 9/15/2025
1.0.1 193 9/14/2025
1.0.0 193 9/14/2025