Skip to main content

Performance

Allocation Comparison

ApproachAllocation per composition
Classic class-based specification1 heap object per And/Or/Not call
Delegate/lambda-based1 closure object per predicate
ZeroAlloc.Specification0 — struct values only

How Zero Allocation Works

Composed specifications are readonly struct values. Structs live on the stack (or inline in the containing object) — they are never allocated on the heap.

// This allocates nothing:
var spec = new ActiveUserSpec().And(new PremiumUserSpec(1000m));
// typeof(spec) == AndSpecification<ActiveUserSpec, PremiumUserSpec, User>
// This is a struct — stack allocated

Expression Tree Allocation

ToExpression() does allocate — expression trees are class objects. This is unavoidable because Expression<Func<T, bool>> is a reference type.

For stateless specs, this can be mitigated by caching:

private static readonly Expression<Func<User, bool>> _cachedExpression =
new ActiveUserSpec().ToExpression();

public Expression<Func<User, bool>> ToExpression() => _cachedExpression;

See Stateless Caching.

Recommendation

  • Use IsSatisfiedBy in hot loops (pure struct evaluation, zero allocation)
  • Call ToExpression() once and cache for ORM queries (expression tree is allocated but reused)