Problem:
A collection of class elements, defined as:
List<Foo> source = new List<Foo>();
public class Foo
{
public string Path {get;set;}
public int Value {get;set;}
}
needs to be compared to a set of rules defined in another class object, defined as:
public class RuleStatement
{
public string Path { get; set; }
public string Operator { get; set; }
public int Value { get; set; }
}
To comply with a Rule specified by a RuleStatement
object, a Foo
element must match the Path
property of a RuleStatement
and a comparison, applied to the Value
property and based on the Operator
property, must return a positive result.
The Operator
property is defined as a string, which makes an implementation based on implicit/explicit operators somewhat convoluted.
The test of the compliance of Foo
elements to the specified rules is also time-sensitive.
A possible solution is to map the Operator
property values to a Func delegate that performs the comparison based on the operator and, possibly, a set of other conditions specific to each operator.
A Dictionary is often used for this kind of task.
In the a simple form, it could be a Dictionary<string, Func<int, int, bool>>
, since two int
values are compared and a true/false
result is expected.
The implementation could also be more generic, if the Foo and RuleStatement
class can be modified/adapted. The Dictionary mapper could also be part of the RuleStatement
class.
For example, make the Foo class implement an Interface that can be used in similar cases:
public interface IGenericFoos
{
string Path { get; set; }
int Value { get; set; }
}
public class Foo : IGenericFoos
{
public string Path { get; set; }
public int Value { get; set; }
}
The RuleStatement
class can be changed in:
public class RuleStatement<T> where T : IGenericFoos
{
public static Dictionary<string, Func<T, RuleStatement<T>, bool>> operators =
new Dictionary<string, Func<T, RuleStatement<T>, bool>>() {
[">="] = (T, R) => T.Value >= R.Value,
["<="] = (T, R) => T.Value <= R.Value,
["<>"] = (T, R) => T.Value != R.Value,
["!="] = (T, R) => T.Value != R.Value,
["=="] = (T, R) => T.Value == R.Value,
["="] = (T, R) => T.Value == R.Value,
["<"] = (T, R) => T.Value < R.Value,
[">"] = (T, R) => T.Value > R.Value,
};
public string Path { get; set; }
public string Operator { get; set; }
public int Value { get; set; }
public bool Eval(T ifoo) => ifoo.Path == Path && operators[Operator](ifoo, this);
}
A LINQ query that evaluates the compliance of all Foo objects to a set of rules can then be simplified to:
var source = new List<Foo> { ... }
var rules = new List<RuleStatement<Foo>> { ... }
// [...]
bool isMatch = rules.All(rule => source.Any(s => rule.Eval(s)));
// Or implicitly:
bool isMatch = rules.All(rule => source.Any(rule.Eval));
Other types of operators can be added to perform different comparison or other operations. E.g., "+" and "-" operators can be evaluated using a fixed value, e.g:
["+"] = (T, R) => T.Value + R.Value >= 0,
["-"] = (T, R) => T.Value - R.Value >= 0,
or using variable value, adding a Property (also specific overloaded Constructors, eventually) to the RuleStatement<T>
class.
This kind of implementation gives more flexibility if more complex comparisons/operations may become necessary in the future.
If these classes cannot be modified, the Dictionary can be used as a stand-alone Field (or whatever fits) and the LINQ query can be changed (keeping the original definition shown in the OP) in:
bool isMatch = rules.All(rule => source
.Any(s => s.Path == rule.Path && operators[rule.Operator](s.Value, rule.Value)));