Back to: Design Patterns in C# With Real-Time Examples
Real-Time Examples of the Interpreter Design Pattern in C#
I will discuss the Real-Time Examples of the Interpreter Design Pattern in C# in this article. Please read our previous article discussing the basic concepts of Interpreter Design Patterns in C# with Examples. At the end of this article, you will understand the following Real-time Examples using the Interpreter Design Pattern in C#.
- Evaluate Permissions Based on a Simple Rule-Based Language
- Evaluating Mathematical Expressions
- Roman Numeral Interpreter
- Simple Markup Language
- Query Language for Filtering a Collection of Products
- Text-Based Role-Playing Game (RPG)
Real-Time Example of Interpreter Design Pattern in C#: Evaluate Permissions Based on a Simple Rule-Based Language
Suppose we are developing a system that evaluates permissions based on a simple rule-based language. This can be a feature in a more extensive content management system or application platform where administrators can define custom permission rules. For example, an organization wants to check if an employee can access a particular resource. Rules are defined as simple expressions:
- role = ‘admin’
- department = ‘finance’ AND years_of_service > 5
- role = ‘manager’ OR (department = ‘HR’ AND years_of_service >= 3)
To implement this, we will represent each rule as an expression and evaluate them. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression: This is the basic unit of our interpreter. public interface IExpression { bool Interpret(Dictionary<string, dynamic> context); } //Terminal Expressions: These represent the terminal expressions in our rule grammar. public class EqualsExpression : IExpression { private string key; private dynamic value; public EqualsExpression(string key, dynamic value) { this.key = key; this.value = value; } public bool Interpret(Dictionary<string, dynamic> context) { return context.ContainsKey(key) && context[key] == value; } } public class GreaterThanExpression : IExpression { private string key; private dynamic value; public GreaterThanExpression(string key, dynamic value) { this.key = key; this.value = value; } public bool Interpret(Dictionary<string, dynamic> context) { return context.ContainsKey(key) && context[key] > value; } } //Non-Terminal Expressions: These combine other expressions. public class AndExpression : IExpression { private IExpression expr1; private IExpression expr2; public AndExpression(IExpression expr1, IExpression expr2) { this.expr1 = expr1; this.expr2 = expr2; } public bool Interpret(Dictionary<string, dynamic> context) { return expr1.Interpret(context) && expr2.Interpret(context); } } public class OrExpression : IExpression { private IExpression expr1; private IExpression expr2; public OrExpression(IExpression expr1, IExpression expr2) { this.expr1 = expr1; this.expr2 = expr2; } public bool Interpret(Dictionary<string, dynamic> context) { return expr1.Interpret(context) || expr2.Interpret(context); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { // Rule: department = 'finance' AND years_of_service > 5 var rule1 = new AndExpression( new EqualsExpression("department", "finance"), new GreaterThanExpression("years_of_service", 5) ); // Sample employee data var employeeData = new Dictionary<string, dynamic> { { "role", "employee" }, { "department", "finance" }, { "years_of_service", 6 } }; // Evaluate the rule bool canAccess = rule1.Interpret(employeeData); Console.WriteLine($"Can Access: {canAccess}"); Console.ReadKey(); } } }
In the real world, the construction of these expressions might be driven by a user interface, configuration file, or another external source. This example showcases the core idea of the Interpreter pattern: breaking down a language (in this case, a rule language) into interpretable expressions. When you run the above code, you will get the following output.
Can Access: True
Real-Time Example of Interpreter Design Pattern in C#: Evaluating Mathematical Expressions
Let’s understand a different real-time example of the Interpreter design pattern in C# using the scenario of evaluating mathematical expressions. We are building a calculator application that allows users to input mathematical expressions, such as “3 + 5 – 2” or “4 * 2 / 2”, and get results. The application should be able to interpret these expressions and produce the correct output. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; namespace InterpreterDesignPattern { //Expression Interface: The primary unit of our interpreter. public interface IExpression { double Interpret(); } //Terminal Expressions: These handle the basic units of our language, namely numbers. public class NumberExpression : IExpression { private double number; public NumberExpression(double number) { this.number = number; } public double Interpret() { return number; } } //Non-Terminal Expressions: These handle operations involving other expressions. public class AddExpression : IExpression { private IExpression leftExpression; private IExpression rightExpression; public AddExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() + rightExpression.Interpret(); } } public class SubtractExpression : IExpression { // Similar to AddExpression, but subtraction is done private IExpression leftExpression; private IExpression rightExpression; public SubtractExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() - rightExpression.Interpret(); } } public class MultiplyExpression : IExpression { // Similar structure, performing multiplication private IExpression leftExpression; private IExpression rightExpression; public MultiplyExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() * rightExpression.Interpret(); } } public class DivideExpression : IExpression { // Similar structure, performing division and checking if the denominator is not zero // Similar structure, performing multiplication private IExpression leftExpression; private IExpression rightExpression; public DivideExpression(IExpression left, IExpression right) { leftExpression = left; rightExpression = right; } public double Interpret() { return leftExpression.Interpret() / rightExpression.Interpret(); } } //Parser: We will create a simple parser to construct our expressions based on the input. public class Parser { public IExpression Parse(string expression) { // This is a rudimentary parser for the sake of the example. In a real-world scenario, this would be more robust. var tokens = expression.Split(' '); IExpression leftExpression = new NumberExpression(double.Parse(tokens[0])); for (int i = 1; i < tokens.Length; i += 2) { switch (tokens[i]) { case "+": leftExpression = new AddExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "-": leftExpression = new SubtractExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "*": leftExpression = new MultiplyExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; case "/": leftExpression = new DivideExpression(leftExpression, new NumberExpression(double.Parse(tokens[i + 1]))); break; } } return leftExpression; } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var parser = new Parser(); var expression = parser.Parse("3 + 5 - 2"); Console.WriteLine($"Result: {expression.Interpret()}"); Console.ReadKey(); } } }
In this example, we’ve created a rudimentary arithmetic expression interpreter. In real-world situations, the parser would need to handle more complex scenarios, possibly with parentheses or order of operations. But this showcases the core principles of the Interpreter pattern: representing and interpreting a language. When you run the above code, you will get the following output.
Result: 6
Real-Time Example of Interpreter Design Pattern in C#: Roman Numeral Interpreter
let’s understand another real-time example of the Interpreter design pattern: a “Roman numeral interpreter” that can convert Roman numerals to their decimal counterparts. We want a system where users can input Roman numerals like “IX” or “XLII”, and the system should be able to convert these numerals into decimal numbers, i.e., 9 and 42, respectively. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression Interface: The primary unit of our interpreter. public abstract class Expression { public abstract string One(); public abstract string Four(); public abstract string Five(); public abstract string Nine(); public abstract int Multiplier(); public void Interpret(Context context) { if (context.Input.Length == 0) return; if (context.Input.StartsWith(Nine())) { context.Output += (9 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Four())) { context.Output += (4 * Multiplier()); context.Input = context.Input.Substring(2); } else if (context.Input.StartsWith(Five())) { context.Output += (5 * Multiplier()); context.Input = context.Input.Substring(1); } while (context.Input.StartsWith(One())) { context.Output += (1 * Multiplier()); context.Input = context.Input.Substring(1); } } } public class Context { public string Input { get; set; } public int Output { get; set; } } // Concrete Expressions: These handle specific Roman numerals public class ThousandExpression : Expression { public override string One() => "M"; public override string Four() => " "; public override string Five() => " "; public override string Nine() => " "; public override int Multiplier() => 1000; } public class HundredExpression : Expression { public override string One() => "C"; public override string Four() => "CD"; public override string Five() => "D"; public override string Nine() => "CM"; public override int Multiplier() => 100; } public class TenExpression : Expression { public override string One() => "X"; public override string Four() => "XL"; public override string Five() => "L"; public override string Nine() => "XC"; public override int Multiplier() => 10; } public class OneExpression : Expression { public override string One() => "I"; public override string Four() => "IV"; public override string Five() => "V"; public override string Nine() => "IX"; public override int Multiplier() => 1; } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { string roman = "MCMXXVIII"; //1928 Context context = new Context { Input = roman }; // Build the 'parse tree' List<Expression> tree = new List<Expression> { new ThousandExpression(), new HundredExpression(), new TenExpression(), new OneExpression() }; // Interpret foreach (Expression exp in tree) { exp.Interpret(context); } Console.WriteLine($"{roman} = {context.Output}"); Console.ReadKey(); } } }
In this example, each numeral class (like ThousandExpression, HundredExpression, etc.) knows how to interpret specific symbols. The main program sets up the context and then uses these interpreter classes in a specific order, ensuring that larger numerals are processed before smaller ones. When you run the above code, you will get the following output.
MCMXXVIII = 1928
Real-Time Example of Interpreter Design Pattern in C#: Simple Markup Language
Imagine you are working on a lightweight documentation system that allows users to define document structures in a simple markup language. This language might define headers, paragraphs, and bold or italic text. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; namespace InterpreterDesignPattern { //Expression Interface: This will be our primary interpretative unit. public interface IExpression { string Interpret(string context); } //Concrete Expressions: These will handle specific patterns in our markup. public class HeaderExpression : IExpression { public string Interpret(string context) { return context.Replace("# ", "<h1>") + "</h1>"; } } public class BoldExpression : IExpression { public string Interpret(string context) { return context.Replace("*", "<b>"); } } public class ItalicExpression : IExpression { public string Interpret(string context) { return context.Replace("_", "<i>"); } } //Interpreter: This class will utilize our expressions to interpret the entire context. public class MarkupInterpreter { private List<IExpression> expressions; public MarkupInterpreter() { expressions = new List<IExpression> { new HeaderExpression(), new BoldExpression(), new ItalicExpression() }; } public string Interpret(string context) { foreach (var expression in expressions) { context = expression.Interpret(context); } return context; } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { string markup = @" # This is a header This is a regular paragraph. *This is a bold text.* _This is an italic text._"; var interpreter = new MarkupInterpreter(); var html = interpreter.Interpret(markup); Console.WriteLine(html); Console.ReadKey(); } } }
This is a very simple example. In a real-world scenario, more advanced parsing and error-handling mechanisms would be necessary to ensure the correctness and safety of the interpreted text. This example is to help you understand the core concept of interpreting content using the Interpreter design pattern. When you run the above code, you will get the following output.
Real-Time Example of Interpreter Design Pattern in C#: Query Language for Filtering a Collection of Products
Let’s consider another real-world example: interpreting a domain-specific query language for filtering a collection of products. Suppose we have an e-commerce platform and want to provide users with a way to filter products using a custom query language. Our DSL might be:
brand:Nike; color:blue; minprice:100; maxprice:300
This would filter products of the Nike brand that are blue and priced between 100 and 300. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; using System.Collections.Generic; using System.Linq; namespace InterpreterDesignPattern { //Product Class: Represents a product in our e-commerce system. public class Product { public string Brand { get; set; } public string Color { get; set; } public decimal Price { get; set; } // ... other properties } //Expression Interface: The primary interface for our interpreter. public interface IExpression { bool Interpret(Product product); } //Concrete Expressions public class BrandExpression : IExpression { private readonly string brand; public BrandExpression(string brand) { this.brand = brand; } public bool Interpret(Product product) { return product.Brand == brand; } } public class ColorExpression : IExpression { private readonly string color; public ColorExpression(string color) { this.color = color; } public bool Interpret(Product product) { return product.Color == color; } } public class PriceRangeExpression : IExpression { private readonly decimal minPrice; private readonly decimal maxPrice; public PriceRangeExpression(decimal min, decimal max) { minPrice = min; maxPrice = max; } public bool Interpret(Product product) { return product.Price >= minPrice && product.Price <= maxPrice; } } //Compound Expression (For combining our criteria) public class AndExpression : IExpression { private readonly List<IExpression> expressions; public AndExpression(params IExpression[] expressions) { this.expressions = new List<IExpression>(expressions); } public bool Interpret(Product product) { return expressions.All(expr => expr.Interpret(product)); } } //Parser: Parser (to convert user's DSL to a compound expression) public class ProductFilterParser { public IExpression Parse(string input) { var criteria = input.Split(';'); var expressions = new List<IExpression>(); foreach (var criterion in criteria) { var parts = criterion.Trim().Split(':'); if (parts[0] == "brand") { expressions.Add(new BrandExpression(parts[1].Trim())); } else if (parts[0] == "color") { expressions.Add(new ColorExpression(parts[1].Trim())); } else if (parts[0] == "minprice") { decimal minPrice = decimal.Parse(parts[1]); expressions.Add(new PriceRangeExpression(minPrice, decimal.MaxValue)); } else if (parts[0] == "maxprice") { decimal maxPrice = decimal.Parse(parts[1]); expressions.Add(new PriceRangeExpression(decimal.MinValue, maxPrice)); } } return new AndExpression(expressions.ToArray()); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var products = new List<Product> { new Product { Brand = "Nike", Color = "blue", Price = 150M }, new Product { Brand = "Adidas", Color = "red", Price = 90M }, new Product { Brand = "Nike", Color = "black", Price = 250M }, // ... other products }; var query = "brand:Nike; color:blue; minprice:100; maxprice:300"; var parser = new ProductFilterParser(); var expression = parser.Parse(query); var filteredProducts = products.Where(p => expression.Interpret(p)).ToList(); foreach (var product in filteredProducts) { Console.WriteLine($"Found: {product.Brand} {product.Color} priced at ${product.Price}"); } Console.ReadKey(); } } }
This is a simplified approach for demonstration purposes. In a real-world scenario, you’d need more extensive error handling, possibly more complex parsing strategies, and other considerations. When you run the above code, you will get the following output.
Found: Nike blue priced at $150
Real-Time Example of Interpreter Design Pattern in C#: Text-Based Role-Playing Game (RPG)
let’s explore the problem of a text-based role-playing game (RPG) where players give commands in sentences to control their character. In our text-based RPG, players can issue commands like:
- move north 10 steps
- attack dragon with sword
- pick gold
We aim to interpret these sentences and convert them into actions in the game. Let us see how we can implement the above example using the Interpreter Design Pattern in C#:
using System; namespace InterpreterDesignPattern { //Context Class: //This class will encapsulate the current state or context of the interpretation. public class Context { public string Action { get; set; } public string Target { get; set; } public string ToolOrDirection { get; set; } public int Quantity { get; set; } // ... any other context-specific properties. } //Expression Interface: //This will define our basic contract for interpreting player commands. public interface IExpression { void Interpret(Context context); } //Concrete Expressions public class MoveExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is moving {context.ToolOrDirection} by {context.Quantity} steps."); } } public class AttackExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is attacking {context.Target} using {context.ToolOrDirection}."); } } public class PickExpression : IExpression { public void Interpret(Context context) { Console.WriteLine($"Character is picking up {context.Target}."); } } //Parser public class CommandInterpreter { public IExpression Parse(string command, Context context) { var words = command.Split(' '); if (command.StartsWith("move")) { context.Action = "move"; context.ToolOrDirection = words[1]; context.Quantity = Convert.ToInt32(words[2]); return new MoveExpression(); } else if (command.StartsWith("attack")) { context.Action = "attack"; context.Target = words[1]; context.ToolOrDirection = words[3]; return new AttackExpression(); } else if (command.StartsWith("pick")) { context.Action = "pick"; context.Target = words[1]; return new PickExpression(); } throw new ArgumentException("Invalid command"); } } // Testing the Interpreter Design Pattern // Client Code public class Client { public static void Main() { var commands = new[] { "move north 10 steps", "attack dragon with sword", "pick gold" }; var interpreter = new CommandInterpreter(); foreach (var command in commands) { var context = new Context(); var expression = interpreter.Parse(command, context); expression.Interpret(context); } Console.ReadKey(); } } }
This is a simple example, but it illustrates the principle. In a real-world RPG, you’d likely have more complex commands, richer context, and more sophisticated parsing logic. The Interpreter Design Pattern would help keep your parsing logic structured and maintainable. When you run the above code, you will get the following output.
In the next article, I will discuss the Mediator Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Interpreter Design Patterns in C#. I hope you enjoy this Real-Time Example of the Interpreter Design Pattern using the C# article.