CSharpEssentials.Rules
3.0.5
dotnet add package CSharpEssentials.Rules --version 3.0.5
NuGet\Install-Package CSharpEssentials.Rules -Version 3.0.5
<PackageReference Include="CSharpEssentials.Rules" Version="3.0.5" />
<PackageVersion Include="CSharpEssentials.Rules" Version="3.0.5" />
<PackageReference Include="CSharpEssentials.Rules" />
paket add CSharpEssentials.Rules --version 3.0.5
#r "nuget: CSharpEssentials.Rules, 3.0.5"
#:package CSharpEssentials.Rules@3.0.5
#addin nuget:?package=CSharpEssentials.Rules&version=3.0.5
#tool nuget:?package=CSharpEssentials.Rules&version=3.0.5
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,
Funcfield, 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.
1. Class (recommended for complex or injectable rules)
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 | 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 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. |
-
.NETStandard 2.1
- CSharpEssentials.Core (>= 3.0.5)
- CSharpEssentials.Results (>= 3.0.5)
- System.Text.Json (>= 9.0.4)
-
net10.0
- CSharpEssentials.Core (>= 3.0.5)
- CSharpEssentials.Results (>= 3.0.5)
-
net11.0
- CSharpEssentials.Core (>= 3.0.5)
- CSharpEssentials.Results (>= 3.0.5)
-
net9.0
- CSharpEssentials.Core (>= 3.0.5)
- CSharpEssentials.Results (>= 3.0.5)
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 |