An embeddable C# expression evaluator with
compiler-style binding for your .NET types.
Alder evaluates C# expressions and statement blocks at runtime against the objects your code supplies, following ECMA-334 7th edition C# semantics. Your security policy and execution limits gate every call. Zero third-party runtime dependencies.
C# semantics·AOT-aware·Async-aware·Dynamic LINQ·Zero deps
using Alder; // Your app supplies the data. The rule author supplies the C# fragment. var cart = new { Subtotal = 120m, Discount = 20m, Tax = 8m, ItemCount = 3, PostalCode = "94107" }; var total = "cart.Subtotal - cart.Discount + cart.Tax" .Evaluate<decimal>(new { cart }); // 108m var shipping = """ var total = cart.Subtotal - cart.Discount + cart.Tax; if (total >= freeShippingMinimum) return "free-shipping"; if (cart.ItemCount > 20) return "bulk-review"; return "standard"; """.Evaluate<string>( new { cart, freeShippingMinimum = 100m }); // free-shipping
using Alder; using Alder.Compiled; // The host owns policy, services, AOT metadata, and compiled reuse. using var engine = new AlderEngine(options => { options.UseCompiler(); options.Security = SecurityOptions.Trusted(); options.Modules.Register<TaxModule>("tax"); options.Aot.UseGeneratedContext(RulesAotContext.Default); }); var totalRule = "cart.Subtotal - cart.Discount + cart.Tax"; var sampleCart = new Cart { Subtotal = 120m, Discount = 20m, Tax = 8m }; engine.SetVariable<Cart>("cart", sampleCart); if (!engine.TryValidate(totalRule, out var diagnostics)) return diagnostics; var total = engine.Evaluate<decimal>(totalRule, new { cart = sampleCart }); engine.SetVariable<Cart>("cart", sampleCart); var trace = engine.EvaluateWithTrace(totalRule); Console.WriteLine(trace.Result); // 108m
using Alder; using Alder.Compiled; // The same cart vocabulary works in Dynamic LINQ pipelines. using var engine = new AlderEngine(options => options.UseCompiler()); var cartsForReview = await db.Carts .WhereDynamic( engine, "Subtotal - Discount >= @0", 100m) .OrderByDynamic<Cart, int>(engine, "ItemCount") .SelectDynamic<Cart, CartReviewRow>( engine, "new { Id, Subtotal, Discount, ItemCount }") .ToListAsync(); // IQueryable<T> sources export expression trees. EF Core translates // provider-safe shapes to SQL; IEnumerable<T> executes in process.
using Alder; // Extended mode adds compact rule syntax one operator at a time. using var engine = new AlderEngine(options => options.LanguageMode = LanguageMode.Extended); var cart = new Cart { Subtotal = 160m, Discount = 20m, ItemCount = 3, PostalCode = "94107", CouponCode = "SHIP-2026", Channel = "mobile", Region = "bay-area" }; var couponMatches = engine.Evaluate<bool>( """cart.CouponCode =~ "^SHIP-[0-9]{4}$" """, new { cart }); var localPostalCode = engine.Evaluate<bool>( """cart.PostalCode like "94%" """, new { cart }); var normalOrderSize = engine.Evaluate<bool>( "cart.ItemCount between 1 and 20", new { cart }); var knownChannel = engine.Evaluate<bool>( """cart.Channel in new[] { "web", "mobile" }""", new { cart }); var safeRegion = engine.Evaluate<bool>( """cart.Region not in new[] { "blocked", "manual-review" }""", new { cart }); var net = engine.Evaluate<decimal>( "let net = cart.Subtotal - cart.Discount in net", new { cart }); var oddSquares = engine.Evaluate<int[]>( "[x * x for x in 1..=5 if x % 2 == 1]");
Built like a compiler, embedded like a library.
Parser and binder build a semantic model: type resolution, overload resolution, conversions, control-flow shape, and assignment legality. Execution paths (interpreter, compiled delegate, expression-tree export) share that model, validation rules, security policy, and execution limits. Divergence is a defect.
Async inside expressions
EvaluateAsync awaits expression-level asynchronous work directly inside the expression itself. await Task<T>, iterators (yield return/yield break), cancellation, and execution limits all flow through the interpreter.
Dynamic LINQ over IQueryable
WhereDynamic, OrderByDynamic, and SelectDynamic bind through the same parser as scalar expressions. IQueryable<T> exports expression trees; providers translate.
Expression-tree export
Alder emits Expression<TDelegate> trees that LINQ providers translate. EF Core consumes the verified shapes: filters, ordering, projections, joins, paging, string methods, EF.Property<T>.
Host-controlled security policy
SecurityOptions controls expression authority. Allow and deny lists cover concrete types and namespaces. Reflection metadata is blocked at evaluation boundaries; the default deny surface covers IO, networking, interop, and dynamic code generation.
Execution constraints
ExecutionConstraints bounds work across interpreter, compiled backend, and generated dispatch. Exceeded limits throw AlderExecutionLimitException with the limit type, configured limit value, observed value, executed statement count, and elapsed time.
NativeAOT generated dispatch
The source generator reads [AlderRegistered] declarations on a partial AlderTypeContext and emits reflection-free dispatch code. Register the types and methods your expressions reach; what isn’t registered falls back to reflection on JIT and surfaces an explicit ALDR0316/0317/0318 diagnostic under AOT.
Extended language mode
Opt into LanguageMode.Extended when host-owned rules need compact predicates: regex matching, SQL-style in/not in, like, between, and let ... in.
Child engines
engine.CreateChild() returns a new AlderEngine that shares the parent’s configuration (parser, binder, security policy, constraints, compiled caches) with its own isolated variable scope. Use it for per-request sandboxing or concurrent evaluation against shared base config.
Step-through evaluation traces
EvaluateWithTrace(...) returns an evaluated tree. Render it when a rule author needs to see which subexpression produced the final value.
A few real snippets, end to end.
The snippets keep following the same cart rule from the first look, then show the public surfaces around it: the runtime, optional Alder.Compiled APIs, Dynamic LINQ, diagnostics, and the source generator for AOT generated dispatch.
One package. Zero third-party runtime dependencies.
Targets net8.0 and netstandard2.0. The Alder package is the single public package. It includes the runtime, the optional Alder.Compiled APIs for JIT-capable consumers, and the source generator for AOT generated dispatch metadata. No third-party runtime dependencies on net8.0; netstandard2.0 pulls Microsoft BCL polyfills (System.Collections.Immutable, Microsoft.Bcl.AsyncInterfaces, System.Threading.Tasks.Extensions).