Created
November 18, 2025 10:29
-
-
Save klinkby/3b1217cbbcc1ad7ac6043416c4c8f3b2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // output: | |
| // | |
| // -2: Result='-2 is even' was found using Rule='Even' because 'We found an even number'. | |
| // -1: Result='default value' was found using Rule='OrElse' because 'No rule matched'. | |
| // 0: Result='0 is even' was found using Rule='Even' because 'We found an even number'. | |
| // 1: Result='3,141592653589793' was found using Rule='Divide' because 'Division rule applied'. | |
| // 2: Result='2 too high' was found using Rule='Limit' because 'The limiter was applied'. | |
| var ruleChain = | |
| new Limit( | |
| new Even( | |
| new Divide( | |
| new OrElse()))); | |
| for(int n = -2; n <= 2; n++) | |
| { | |
| Result result = ruleChain.Apply(n); | |
| Console.WriteLine($"{n: 0;-#}: Result='{result.Value}' found using '{result.Rule}' rule because '{result.Summary}'."); | |
| } | |
| /// <summary> | |
| /// Result and metadata from rule engine match. | |
| /// </summary> | |
| /// <param name="Value">Some complex result Value.</param> | |
| /// <param name="Rule">Name of triggered rule.</param> | |
| /// <param name="Summary">Explanation of the rule.</param> | |
| internal record struct Result(string Value, string Rule, string Summary) | |
| { | |
| public readonly static Result Empty = new(); | |
| } | |
| /// <summary> | |
| /// Defines a rule in the chain of responsibility. | |
| /// </summary> | |
| /// <param name="Summary">Description of what triggers this rule.</param> | |
| /// <param name="next">Next rule to apply if not triggered by this.</param> | |
| internal abstract class Rule(string Summary, Rule? next) | |
| { | |
| /// <summary> | |
| /// Returns a result value if the given input matches the rule's conditions. | |
| /// </summary> | |
| /// <param name="input">The input value to be evaluated against the rule.</param> | |
| /// <returns> | |
| /// An object representing the result of the match if the rule's conditions are met; otherwise, null. | |
| /// </returns> | |
| protected abstract string? Match(int input); | |
| /// <summary> | |
| /// Applies the rule to the given input and returns the result and metadata if the rule's conditions are met | |
| /// or passes the input to the next rule in the chain. | |
| /// </summary> | |
| /// <param name="input">The input value to be evaluated by the rule.</param> | |
| /// <returns> | |
| /// A <see cref="Result"/> object containing the value, rule name, and summary if the rule's conditions are met; | |
| /// otherwise, the result of the next rule in the chain, | |
| /// or null if no rule matches. | |
| /// </returns> | |
| public Result Apply(int input) | |
| { | |
| string? resultValue = Match(input); | |
| return resultValue is not null | |
| ? new Result(resultValue, GetType().Name, Summary) | |
| : next?.Apply(input) ?? Result.Empty; | |
| } | |
| } | |
| internal class Divide(Rule next) : Rule("Division rule applied", next) | |
| { | |
| protected override string? Match(int input) => | |
| input > 0 ? $"{Math.PI / input}" : null; | |
| } | |
| internal class Even(Rule next) : Rule("We found an even number", next) | |
| { | |
| protected override string? Match(int input) => | |
| input % 2 == 0 ? $"{input} is even" : null; | |
| } | |
| internal class Limit(Rule next) : Rule("The limiter was applied", next) | |
| { | |
| protected override string? Match(int input) => | |
| input > 1 ? $"{input} too high" : null; | |
| } | |
| internal class OrElse() : Rule("No rule matched", null) | |
| { | |
| protected override string Match(int input) => | |
| "default value"; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment