DeepEqual.Generator
1.0.3
dotnet add package DeepEqual.Generator --version 1.0.3
NuGet\Install-Package DeepEqual.Generator -Version 1.0.3
<PackageReference Include="DeepEqual.Generator" Version="1.0.3" />
<PackageVersion Include="DeepEqual.Generator" Version="1.0.3" />
<PackageReference Include="DeepEqual.Generator" />
paket add DeepEqual.Generator --version 1.0.3
#r "nuget: DeepEqual.Generator, 1.0.3"
#:package DeepEqual.Generator@1.0.3
#addin nuget:?package=DeepEqual.Generator&version=1.0.3
#tool nuget:?package=DeepEqual.Generator&version=1.0.3
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
andTicks
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:
OrderInsensitiveCollections
→ falseIncludeInternals
→ falseIncludeBaseMembers
→ trueCycleTracking
→ true
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
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 4.14.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.