Interpreter Design Pattern in .NET Core API
A practical Interpreter pattern guide in C# with AST and Context-driven variants for expression-based workflows.

Intro
When systems need to evaluate rules, filters, or mini-languages, embedding conditions directly in services quickly becomes hard to maintain. The Interpreter pattern models expressions as objects so evaluation logic stays organized.
What Is the Interpreter Pattern?
Interpreter is a behavioral design pattern that defines a representation for grammar rules and evaluates sentences in that grammar.
In .NET terms, this often appears in expression trees, filtering DSLs, rule engines, and query builders.
Interpreter Class Example
This example evaluates simple rules like amount > 100 and vip.
public sealed class RuleContext{ public decimal Amount { get; init; } public bool IsVip { get; init; }}
public interface IExpression{ bool Interpret(RuleContext context);}
public sealed class AmountGreaterThan(decimal threshold) : IExpression{ public bool Interpret(RuleContext context) => context.Amount > threshold;}
public sealed class VipExpression : IExpression{ public bool Interpret(RuleContext context) => context.IsVip;}
public sealed class AndExpression(IExpression left, IExpression right) : IExpression{ public bool Interpret(RuleContext context) => left.Interpret(context) && right.Interpret(context);}Why This Works
- grammar rules are represented as reusable objects
- complex expressions are composed from smaller expressions
- evaluation is centralized around a consistent context
When to Use It
- building lightweight rule engines
- evaluating query/filter expressions
- creating domain-specific language (DSL) evaluators
- translating user-defined rules into executable logic
Avoid this pattern when rules are simple and static.
Important Note for ASP.NET Core
In ASP.NET Core APIs, parse user input into expression objects inside the application layer, then evaluate safely. This is a common approach in advanced search endpoints and policy-like rule evaluation.
Also, real-world interpreter usage appears in:
- regex engines (interpreting pattern tokens)
- LINQ expression trees (
Expression<Func<T, bool>>)
Core Components of the Pattern
| Part | Purpose | Example in this article |
|---|---|---|
| Abstract expression | Defines interpret operation | IExpression |
| Terminal expression | Evaluates direct rule values | VipExpression, AmountGreaterThan |
| Non-terminal expression | Composes sub-expressions | AndExpression |
| Context | Holds runtime data for interpretation | RuleContext |
| Client | Builds expression tree and executes | API service |
Common Interpreter Variations
Abstract Syntax Tree Interpreter
Rules are parsed into an AST and interpreted recursively.
public sealed class OrExpression(IExpression left, IExpression right) : IExpression{ public bool Interpret(RuleContext context) => left.Interpret(context) || right.Interpret(context);}Context-driven Interpreter
Expressions remain simple, and richer runtime behavior is carried by context objects.
public sealed class RegionContext : RuleContext{ public string Region { get; init; } = "IN";}How the Variations Differ
| Variation | Model | Best use case |
|---|---|---|
| AST Interpreter | Tree of expression nodes | Structured grammar and operator precedence |
| Context-driven Interpreter | Simpler expressions, richer context | Business rules dependent on runtime metadata |
SOLID Principles Behind It
SRP - Single Responsibility Principle
Each expression class handles one grammar concept.
OCP - Open/Closed Principle
Add new expression types without modifying existing nodes.
DIP - Dependency Inversion Principle
Clients depend on IExpression, not concrete rule nodes.
Advantages and Disadvantages
Advantages
- keeps rule logic composable
- supports incremental grammar growth
- enables unit testing at expression level
Disadvantages (and Optional Alternatives)
- full grammar parsing can become complex Optional pattern: Specification (for simpler predicate composition).
- deep expression trees may affect readability Optional pattern: Strategy (when only interchangeable algorithms are needed).
UML Diagram
classDiagram class Client class RuleContext class IExpression { <<interface>> +Interpret(context) } class AmountGreaterThan class VipExpression class AndExpression
Client --> IExpression IExpression <|.. AmountGreaterThan IExpression <|.. VipExpression IExpression <|.. AndExpression AndExpression --> IExpression : left/right IExpression --> RuleContextA Quick Usage Example
IExpression rule = new AndExpression( new AmountGreaterThan(100), new VipExpression());
var allowed = rule.Interpret(new RuleContext { Amount = 240, IsVip = true });Console.WriteLine(allowed); // TrueHow to Validate the Pattern
- each expression should be testable independently
- composed expression trees should produce deterministic output
- parser output should map correctly to expression objects
- context should remain explicit and free from hidden global state
- adding a new operator should not require rewriting existing nodes
Summary
The Interpreter pattern helps .NET applications model and execute expression-based rules cleanly. With AST and context-driven variants, you can scale from simple filters to richer domain-specific rule engines.




