NSpecifications 2.0.0

dotnet add package NSpecifications --version 2.0.0
                    
NuGet\Install-Package NSpecifications -Version 2.0.0
                    
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="NSpecifications" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NSpecifications" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="NSpecifications" />
                    
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 NSpecifications --version 2.0.0
                    
#r "nuget: NSpecifications, 2.0.0"
                    
#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 NSpecifications@2.0.0
                    
#: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=NSpecifications&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NSpecifications&version=2.0.0
                    
Install as a Cake Tool

NSpecifications

A .NET library implementing the Specification pattern — small, composable, named rule objects that encapsulate query logic. Specifications (specs) work identically against in-memory collections (IEnumerable<T>) and (database) queries (IQueryable<T>), compose with &, |, and ! operators, and are well suited to be used with LINQ.

Install

dotnet add package NSpecifications

Or via Package Manager Console:

Install-Package NSpecifications

Features

Creating a Spec<T>

A Spec<T> wraps a predicate expression and can be used directly anywhere a collection needs filtering:

var highRated = new Spec<Product>(p => p.Rating >= 4.5);

products.Where(highRated);                         // in-memory
dbContext.Products.Where(highRated).ToList();      // database — same code

Compose with operators

Specs can be defined inline or split into smaller named parts and combined with &, |, and !:

var electronics = new Spec<Product>(p => p.Category == ProductCategory.Electronics);
var affordable  = new Spec<Product>(p => p.Price < 500m);

var affordableElectronics = electronics & affordable;   // AND
var anyGoodDeal           = electronics | affordable;   // OR
var expensiveElectronics  = electronics & !affordable;  // NOT

Drop in as Expression<Func<T, bool>> or Func<T, bool>

Spec<T> implicitly converts to both delegate types, so it can be passed anywhere they are expected.

var affordable = new Spec<Product>(p => p.Price < 500m);

repository.Find(affordable);               // where Find expects Expression<Func<Product, bool>>
products.Where(affordable).ToList();       // works as Func<Product, bool> too

Conditional negation with == and !=

The == and != operators conditionally negate a spec based on a bool value, which is useful for optional filters. spec == true and spec != false both return the spec as-is; spec == false and spec != true both return its negation.

// defined as a static member on the entity or in a dedicated specs class
static readonly Spec<Product> Available = new(p => p.IsAvailable);

// isAvailable = null  → all products
// isAvailable = true  → only available
// isAvailable = false → only unavailable
public Product[] FindProducts(bool? isAvailable = null)
{
    var spec = Spec.Any<Product>();

    if (isAvailable.HasValue)
        spec = spec & (Available == isAvailable.Value);

    return _repository.Find(spec);
}

Is / Are extension methods

var inStock = new Spec<Product>(p => p.IsAvailable);

product.Is(inStock);                                  // single object
new[] { product1, product2, product3 }.Are(inStock);  // all must satisfy

Universal operators on ISpecification<T>

Operators (!, &, |, ==, !=) also work on any ISpecification<T> implementation via C# extension members.

ISpecification<Product> electronics = new Spec<Product>(p => p.Category == ProductCategory.Electronics);
ISpecification<Product> highRated   = new Spec<Product>(p => p.Rating > 4.0);

var combined = electronics & highRated;

Note: ISpecification<T> operators return ISpecification<T>, which cannot be converted to an Expression<Func<T, bool>>. See Spec<T> vs ISpecification<T> operators in the Technical Reference.

Real Use Case

Specs are most useful when stored close to the entity they describe — as static members, in a dedicated ProductSpecs class, or in a shared Specs class. Here is a complete example:

public class Product
{
    public string Name { get; }
    public ProductCategory Category { get; }
    public bool IsAvailable { get; }

    public static readonly Spec<Product> Available = new(p => p.IsAvailable);

    public static Spec<Product> InCategory(ProductCategory category) =>
        new(p => p.Category == category);
}
public IEnumerable<Product> FindProducts(ProductCategory? category = null, bool? isAvailable = null)
{
    var spec = Spec.Any<Product>();

    if (category.HasValue)
        spec = spec & Product.InCategory(category.Value);

    if (isAvailable.HasValue)
        spec = spec & (Product.Available == isAvailable.Value);

    return _repository.Find(spec);
}

Specs defined as static readonly fields are instantiated once and reused. Specs that depend on a parameter are factory methods that create a new instance per call. Neither needs to be mocked in unit tests.

Technical Reference

Spec<T>

Spec<T> is a record that stores a predicate expression, making it implicitly convertible to Expression<Func<T, bool>> for use with IQueryable<T> providers (e.g. Entity Framework), and to Func<T, bool> for any method expecting a predicate delegate.

Operators on Spec<T> always return a new Spec<T>, so the stored expression is preserved through composition.

Naming tip: following Eric Evans' convention, name specs as objects rather than predicates — affordable, not isAffordable. A spec is more than a boolean; it's a reusable, composable rule.

ISpecification<T>

For cases where implicit conversion to Expression<Func<T, bool>> is not needed, any class can implement ISpecification<T> directly:

public class InStockSpec : ISpecification<Product>
{
    public bool IsSatisfiedBy(Product product) =>
        product.IsAvailable && product.StockQuantity > 0;
}

Composition via .And(), .Or(), .Not() extension methods and operators works the same way, but results are ISpecification<T> — suitable for in-memory validation only.

Spec<T> vs ISpecification<T> operators

Both types support operators, but the return type is determined by the declared type of the operands:

Left operand Right operand Result Implicitly convertible?
Spec<T> Spec<T> Spec<T> ✅ Yes
ISpecification<T> ISpecification<T> ISpecification<T> ❌ No
Spec<T> ISpecification<T> ISpecification<T> ❌ No
ISpecification<T> Spec<T> ISpecification<T> ❌ No
Spec<Product> available   = new(p => p.IsAvailable);
Spec<Product> electronics = new(p => p.Category == ProductCategory.Electronics);

var availableElectronics = available & electronics;             // Spec<T>            ✅
dbContext.Products.Where(availableElectronics).ToList();

ISpecification<Product> custom = new MyCustomSpec();
var availableAndCustom = available & custom;                    // ISpecification<T>  ❌
dbContext.Products.Where(availableAndCustom);                   // won't compile
inMemoryList.Where(availableAndCustom.IsSatisfiedBy);           // ✅ in-memory only

Rule of thumb: keep all operands as Spec<T> when database queries are involved.

Pre-built specifications

Spec.Any<Product>()                        // always satisfied  — also Spec<Product>.Any
Spec.None<Product>()                       // never satisfied   — also Spec<Product>.None
Spec.Create<Product>(p => p.IsAvailable)   // explicit factory  — also Spec<Product>.Create(...)

References

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (7)

Showing the top 5 NuGet packages that depend on NSpecifications:

Package Downloads
Blowin.GenericRepository

Package Description

Blowin.GenericRepository.EFCore

Package Description

idee5.Globalization

Globalization extensions. Enables database support for localization resources and parlances for industries and customers..

CqrsEase.Common.Queries

Contains generic definition of widely used queries for retrieving single or range of objects

idee5.Globalization.EFCore

Entity Framework Core context provider for idee5.Globalization.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 92 6/18/2026
1.2.1 53,501 3/23/2025
1.2.0 70,430 7/1/2024
1.1.0 420,275 5/30/2018
1.0.1 2,310 1/2/2018
1.0.0 1,862 1/2/2018