ZeroAlloc.ValueObjects
1.1.1
See the version list below for details.
dotnet add package ZeroAlloc.ValueObjects --version 1.1.1
NuGet\Install-Package ZeroAlloc.ValueObjects -Version 1.1.1
<PackageReference Include="ZeroAlloc.ValueObjects" Version="1.1.1" />
<PackageVersion Include="ZeroAlloc.ValueObjects" Version="1.1.1" />
<PackageReference Include="ZeroAlloc.ValueObjects" />
paket add ZeroAlloc.ValueObjects --version 1.1.1
#r "nuget: ZeroAlloc.ValueObjects, 1.1.1"
#:package ZeroAlloc.ValueObjects@1.1.1
#addin nuget:?package=ZeroAlloc.ValueObjects&version=1.1.1
#tool nuget:?package=ZeroAlloc.ValueObjects&version=1.1.1
ZeroAlloc.ValueObjects
Zero-allocation source-generated ValueObject equality for your existing domain types.
Same performance as record — without forcing the record keyword on your domain model.
The problem
CSharpFunctionalExtensions.ValueObject uses IEnumerable<object> GetEqualityComponents() for equality. Every Equals() or GetHashCode() call allocates an iterator state machine and boxes every value-type property. In hot paths — dictionary keys, HashSets, LINQ grouping — this creates significant GC pressure.
Why not just use record?
record gives you zero-allocation equality, but comes with trade-offs:
record |
ZeroAlloc.ValueObjects |
|
|---|---|---|
| Zero allocation | ✓ | ✓ |
Works on existing class/struct |
✗ — forces record keyword |
✓ |
| Can inherit from non-record base | ✗ | ✓ |
| Fine-grained member control | ✗ | [EqualityMember] / [IgnoreEqualityMember] |
| No extra generated members | ✗ — adds EqualityContract, with, deconstruct |
✓ |
| Struct support | record struct |
partial struct |
If your domain model uses regular classes, adding [ValueObject] to an existing partial class is all you need — no refactoring, no change in type hierarchy.
Install
dotnet add package ZeroAlloc.ValueObjects
Usage
// Works on your existing partial class — no keyword changes
[ValueObject]
public partial class Money
{
public decimal Amount { get; }
public string Currency { get; }
}
// Or as a struct
[ValueObject]
public partial struct CustomerId
{
public Guid Value { get; }
}
// Generated: Equals, GetHashCode (HashCode.Combine), ==, !=, ToString — zero alloc
Benchmarks
ZeroAlloc.ValueObjects matches record and record struct performance exactly. The only allocating variant is CSharpFunctionalExtensions.ValueObject.
| Method | Mean | Allocated |
|---|---|---|
| CFE_Equals | 45.2 ns | 96 B |
| Record_Equals | 3.1 ns | 0 B |
| RecordStruct_Equals | 2.8 ns | 0 B |
| ZeroAlloc_Equals | 3.1 ns | 0 B |
| ZeroAllocStruct_Equals | 2.8 ns | 0 B |
| CFE_GetHashCode | 38.7 ns | 88 B |
| Record_GetHashCode | 2.4 ns | 0 B |
| RecordStruct_GetHashCode | 2.2 ns | 0 B |
| ZeroAlloc_GetHashCode | 2.4 ns | 0 B |
| ZeroAllocStruct_GetHashCode | 2.2 ns | 0 B |
Run your own benchmarks:
dotnet run -c Release --project benchmarks/ZeroAlloc.ValueObjects.Benchmarks
Attributes
| Attribute | Target | Purpose |
|---|---|---|
[ValueObject] |
partial class or partial struct |
Triggers generation |
[ValueObject(ForceClass = true)] |
partial struct |
Force class emission even on a struct declaration |
[EqualityMember] |
Property | Opt-in mode: only marked props participate in equality |
[IgnoreEqualityMember] |
Property | Opt-out mode: exclude this prop from equality |
Default member selection
All public properties with a getter participate by default. If any property is marked [EqualityMember], the mode switches to opt-in and only marked properties are included.
[ValueObject]
public partial class Address
{
[EqualityMember] public string Street { get; }
[EqualityMember] public string City { get; }
public string Notes { get; } // excluded — not marked
}
[ValueObject]
public partial class Product
{
public string Name { get; }
[IgnoreEqualityMember] public string InternalCode { get; } // excluded
}
Generated output
For each [ValueObject] type the generator emits:
bool Equals(object? obj)— direct type check, no boxingbool Equals(T other)—IEquatable<T>fast pathint GetHashCode()—HashCode.Combine(...)for ≤8 props, incrementalHashCode.Add()for 9+operator ==/operator !=string ToString()—"Money { Amount = 10, Currency = USD }"
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- 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.