NSpecifications 2.0.0
dotnet add package NSpecifications --version 2.0.0
NuGet\Install-Package NSpecifications -Version 2.0.0
<PackageReference Include="NSpecifications" Version="2.0.0" />
<PackageVersion Include="NSpecifications" Version="2.0.0" />
<PackageReference Include="NSpecifications" />
paket add NSpecifications --version 2.0.0
#r "nuget: NSpecifications, 2.0.0"
#:package NSpecifications@2.0.0
#addin nuget:?package=NSpecifications&version=2.0.0
#tool nuget:?package=NSpecifications&version=2.0.0
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 returnISpecification<T>, which cannot be converted to anExpression<Func<T, bool>>. SeeSpec<T>vsISpecification<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 | 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 (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.