Manikandan — Manikandan
Updated on 3 min read Manikandan Design Patterns

Interpreter Design Pattern in .NET Core API

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

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

PartPurposeExample in this article
Abstract expressionDefines interpret operationIExpression
Terminal expressionEvaluates direct rule valuesVipExpression, AmountGreaterThan
Non-terminal expressionComposes sub-expressionsAndExpression
ContextHolds runtime data for interpretationRuleContext
ClientBuilds expression tree and executesAPI 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

VariationModelBest use case
AST InterpreterTree of expression nodesStructured grammar and operator precedence
Context-driven InterpreterSimpler expressions, richer contextBusiness 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 --> RuleContext

A 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); // True

How 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.

Share:
Back to Blog

Related Posts

View All Posts »