CSharpEssentials.Rules 3.0.5

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

CSharpEssentials.Rules

Composable, functional rule engine for .NET. Define business logic as small, reusable rules and combine them with a fluent API.

Features

  • Three definition styles — Class, Func field, or inline lambda: all are first-class citizens.
  • No boilerplate conversions — Pass functions directly to RuleEngine; no explicit .ToRule() required at call sites.
  • Linear Sequences — Run rules in order, stop on first failure.
  • Conditional Rules — If/Then/Else branching.
  • And / Or Logic — All must pass, or at least one must pass.
  • Async Support — First-class async rules.
  • Result Integration — Built on CSharpEssentials.Results.

Installation

dotnet add package CSharpEssentials.Rules

Rule Definition Styles

There are three ways to define a rule. All styles are interchangeable — mix and match freely when composing.

Implement IRule<TContext> when a rule needs constructor-injected dependencies, internal state, or readable naming across a large codebase.

public sealed class AgeRule : IRule<UserContext>
{
    public Result Evaluate(UserContext ctx, CancellationToken ct = default) =>
        ctx.Age >= 18 ? Result.Success() : Error.Validation("Age.Underage", "Must be at least 18.");
}

public sealed class LicenseRule : IRule<UserContext>
{
    private readonly ILicenseRepository _repo;

    public LicenseRule(ILicenseRepository repo) => _repo = repo;

    public Result Evaluate(UserContext ctx, CancellationToken ct = default) =>
        _repo.IsValid(ctx.LicenseId) ? Result.Success() : Error.Validation("License.Invalid", "License not found.");
}

2. Func field (reusable, no class needed)

Declare rules as Func<TContext, Result> fields or variables. They can be stored, passed around, and used anywhere an IRule<TContext> is expected — with or without an explicit .ToRule() call.

// Defined once, reused anywhere
Func<UserContext, Result> regionRule = ctx =>
    ctx.IsAllowedRegion ? Result.Success() : Error.Forbidden("Region.Blocked", "Not available in your region.");

// Named method — method group works too
static Result CheckEmail(UserContext ctx) =>
    ctx.Email.Contains('@') ? Result.Success() : Error.Validation("Email.Invalid", "Invalid email address.");

3. Inline lambda (best for one-off or ad-hoc composition)

Write the rule directly at the call site. No variable, no class — maximum density for simple checks.

Result result = RuleEngine.Evaluate(
    (UserContext ctx) => ctx.Age >= 18 ? Result.Success() : Error.Validation("Age.Underage", "Must be at least 18."),
    userCtx);

Passing Rules Without Explicit Conversion

RuleEngine.Evaluate, .And(), .Or(), and .Next() all accept raw Func delegates. You never need to wrap them manually.

var ctx = new UserContext { Age = 20, HasLicense = true, IsAllowedRegion = true };

// Class instance
Result r1 = RuleEngine.Evaluate(new AgeRule(), ctx);

// Func variable — no .ToRule() needed
Result r2 = RuleEngine.Evaluate(regionRule, ctx);

// Inline lambda — no .ToRule() needed
Result r3 = RuleEngine.Evaluate(
    (UserContext c) => c.HasLicense ? Result.Success() : Error.Validation("License.Missing", "License required."),
    ctx);

// Named method group — works directly
Result r4 = RuleEngine.Evaluate(CheckEmail, ctx);

Combining Rules

All three styles compose freely. Mix class instances, Func variables, and lambdas in the same call.

And — all must pass (collects all failures)

// Using class instances
Result andResult = RuleEngine.Evaluate(
    new IRuleBase<UserContext>[] { new AgeRule(), new LicenseRule(repo) }.And(),
    ctx);

// Using Func array — no .ToRule() needed at all
Result andResult2 = RuleEngine.Evaluate(
    new Func<UserContext, Result>[] { regionRule, CheckEmail }.And(),
    ctx);

// Mixed: class + lambda
Result andResult3 = RuleEngine.Evaluate(
    new IRuleBase<UserContext>[]
    {
        new AgeRule(),
        regionRule.ToRule(),
        ((Func<UserContext, Result>)(c => c.HasLicense ? Result.Success() : Error.Validation("License.Missing", "License required."))).ToRule()
    }.And(),
    ctx);

Or — at least one must pass

// Func array — direct, no conversion
Result orResult = RuleEngine.Evaluate(
    new Func<UserContext, Result>[] { regionRule, CheckEmail }.Or(),
    ctx);

Linear — stop on first failure

// Class instances
Result linear = RuleEngine.Evaluate(
    new IRule<UserContext>[] { new AgeRule(), new LicenseRule(repo) }.Linear(),
    ctx);

// Func chaining with .Next() — reads like a pipeline
Result pipeline = RuleEngine.Evaluate(
    ((Func<UserContext, Result>)CheckEmail)
        .Next(regionRule)
        .Next(c => c.Age >= 18 ? Result.Success() : Error.Validation("Age.Underage", "Must be at least 18.")),
    ctx);

Conditional — if/then/else branching

// Class instances
Result conditional = RuleEngine.If(
    new AgeRule(),
    success: new GrantAccessRule(),
    failure: new DenyAccessRule(),
    ctx);

// Func lambdas — all three branches inline
Result conditional2 = RuleEngine.If(
    (UserContext c) => c.Age >= 18 ? Result.Success() : Error.Validation("Age.Underage", "Must be at least 18."),
    success: c => Result.Success(),
    failure: c => Error.Forbidden("Access.Denied", "Access denied."),
    ctx);

// Boolean shorthand
Result conditional3 = RuleEngine.If(
    condition: ctx.IsAllowedRegion,
    success: new GrantAccessRule(),
    failure: new DenyAccessRule(),
    ctx);

Rules with Values (Result<T>)

Rules can produce a typed result alongside success/failure.

// Class form
public sealed class GradeRule : IRule<int, string>
{
    public Result<string> Evaluate(int score, CancellationToken ct = default)
    {
        if (score >= 90) return "A";
        if (score >= 80) return "B";
        return Error.Validation("Grade.Failed", "Score too low.");
    }
}

// Func form — no .ToRule() at call site
Result<string> grade = RuleEngine.Evaluate(
    (int score) => score >= 90 ? Result<string>.Success("A") : Error.Validation("Grade.Failed", "Score too low."),
    85);

Integration with Domain Error Hierarchies

Rules integrate naturally with domain-specific Error factory classes, keeping validation logic close to domain language:

public static class RegistrationErrors
{
    public static readonly Error Underage =
        Error.Validation("Registration.Underage", "Applicant must be at least 18.");

    public static readonly Error NoLicense =
        Error.Validation("Registration.NoLicense", "A valid driver's license is required.");

    public static readonly Error RegionBlocked =
        Error.Forbidden("Registration.RegionBlocked", "Registration is not available in your region.");
}

// Class-based rules referencing the domain error catalogue
public sealed class AgeRule : IRule<ApplicantContext>
{
    public Result Evaluate(ApplicantContext ctx, CancellationToken ct = default) =>
        ctx.Age >= 18 ? Result.Success() : RegistrationErrors.Underage;
}

// Func-based rule — same domain errors, no class needed
Func<ApplicantContext, Result> regionRule =
    ctx => ctx.IsAllowedRegion ? Result.Success() : RegistrationErrors.RegionBlocked;

// Compose: Func array runs all rules and collects all failures
var applicant = new ApplicantContext { Age = 16, HasLicense = false, IsAllowedRegion = true };

Result result = RuleEngine.Evaluate(
    new Func<ApplicantContext, Result>[]
    {
        c => c.Age >= 18 ? Result.Success() : RegistrationErrors.Underage,
        c => c.HasLicense    ? Result.Success() : RegistrationErrors.NoLicense,
        regionRule
    }.And(),
    applicant);

result.Match(
    onSuccess: () => Console.WriteLine("Approved"),
    onError: errors =>
    {
        foreach (Error e in errors)
            Console.WriteLine($"[{e.Type}] {e.Code}: {e.Description}");
    });
// [Validation] Registration.Underage: Applicant must be at least 18.
// [Validation] Registration.NoLicense: A valid driver's license is required.

Rules return Result / Result<T> — the same type used throughout CSharpEssentials — so rule outcomes compose directly with Then, Match, and other chaining operations.

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 is compatible.  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 is compatible.  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.  net11.0 is compatible. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen 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.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CSharpEssentials.Rules:

Package Downloads
CSharpEssentials

A comprehensive C# library enhancing functional programming capabilities with type-safe monads (Maybe, Result), discriminated unions (Any), and robust error handling. Features include: domain-driven design support, enhanced Entity Framework integration, testable time management, JSON utilities, and LINQ extensions. Built for modern C# development with focus on maintainability, testability, and functional programming principles.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.5 119 5/7/2026
3.0.4 114 5/6/2026
3.0.3 120 5/5/2026
3.0.2 130 5/5/2026
3.0.1 134 5/3/2026
3.0.0 124 5/3/2026
2.1.0 294 11/26/2025
2.0.9 247 9/30/2025
2.0.8 219 9/29/2025
2.0.7 223 9/29/2025
2.0.6 222 9/29/2025
2.0.5 219 9/29/2025
2.0.4 220 9/28/2025
2.0.3 235 9/28/2025
2.0.2 230 9/28/2025
2.0.1 225 9/28/2025
2.0.0 221 9/28/2025