Skip to main content

Fluent API

The source generator adds three methods to every [Specification]-attributed struct:

Generated Methods

// For a spec: public readonly partial struct MySpec : ISpecification<T>

public AndSpecification<MySpec, TOther, T> And<TOther>(TOther other)
where TOther : struct, ISpecification<T>;

public OrSpecification<MySpec, TOther, T> Or<TOther>(TOther other)
where TOther : struct, ISpecification<T>;

public NotSpecification<MySpec, T> Not();

Usage

// AND — both must be satisfied
var activeAndPremium = new ActiveUserSpec().And(new PremiumUserSpec(1000m));

// OR — either must be satisfied
var activeOrPremium = new ActiveUserSpec().Or(new PremiumUserSpec(500m));

// NOT — must not be satisfied
var notActive = new ActiveUserSpec().Not();

// Chaining
var spec = new ActiveUserSpec()
.And(new PremiumUserSpec(1000m))
.Or(new AdminUserSpec());

Return Types

Every method returns a concrete struct — no boxing, no heap allocation:

MethodReturn type
.And(other)AndSpecification<MySpec, TOther, T>
.Or(other)OrSpecification<MySpec, TOther, T>
.Not()NotSpecification<MySpec, T>

Because return types are concrete structs, chained compositions also type-check at compile time. Chaining two And calls produces AndSpecification<AndSpecification<A, B, T>, C, T> — the type grows but remains a struct at every level.