Skip to content

Instantly share code, notes, and snippets.

@klinkby
Created November 18, 2025 10:29
Show Gist options
  • Select an option

  • Save klinkby/3b1217cbbcc1ad7ac6043416c4c8f3b2 to your computer and use it in GitHub Desktop.

Select an option

Save klinkby/3b1217cbbcc1ad7ac6043416c4c8f3b2 to your computer and use it in GitHub Desktop.
// 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