Introduction
At work, we had a requirement to evaluate an ad-hoc condition that contained one or more individual and grouped expressions. the expressions would result in us have the following information to work with:
- two objects to compare
- a string that indicated how to compare them
For example, we could end up with two string
objects compared for "==", or two DateTime
objects compared for ">=". The technical problem was, "How do we evaluate the expression?"
This tip describes my solution.
The Evaluator Class
The
Evaluator
class is based on the premise that we know the comparison we'll want to perform, but we DON'T know the types of the objects. I wanted to avoid writing a handful of overloaded methods for each comparison operation, and couldn't find a way to use generic parameters.
To facilitate this paradigm, I created a dictionary that contains strings as keys that indicate the comparison, and delegates that would be used to call the appropriate method. This made any comparison a single function call to the
Evaluate
method, which invokes the appropriate comparison method:
public bool Evaluate(string comparison, object obj1, object obj2)
{
try
{
result = _logic[comparison].Invoke(obj1, obj2);
}
catch (Exception)
{
}
return result;
}
Comparisons were a bit tricky due to the semi-anonymous nature of the objects. Comparisons for equality/inequality were easy, because you could simply cast the objects to and compare them as strings. However, the comparisons for less than and greater than required us to know what type we're actually dealing with because strings can't be directly checked for less/greater-than status.
To establish the correct type, I wrote a method called GetObjectType()
which determines the type of the specified object, and returns an appropriate enum
:
enum ObjectType { String, Numeric, DateTime, Invalid };
private ObjectType GetObjectType(object obj)
{
ObjectType objectType = ObjectType.String;
if (obj is int ||
obj is float ||
obj is double ||
obj is decimal)
{
objectType = ObjectType.Numeric;
}
else if (obj is DateTime)
{
objectType = ObjectType.DateTime;
}
else if (obj is string)
{
objectType = ObjectType.String;
}
else
{
objectType = ObjectType.Invalid;
}
return objectType;
}
Once the object type is determined, we can perform the desired operation:
private bool CompareGreater(object obj1, object obj2)
{
bool result = false;
if (ValidCompare(obj1, obj2))
{
switch (GetObjectType(obj1))
{
case ObjectType.Numeric : { result = (Convert.ToDecimal(obj1) > Convert.ToDecimal(obj2)); } break;
case ObjectType.DateTime : { result = (Convert.ToDateTime(obj1) > Convert.ToDateTime(obj2)); } break;
case ObjectType.String : { result = CompareStrings(obj1, obj2, 1, false); } break;
default : { result = false; } break;
}
}
return result;
}
You may have noticed the call to CompareStrings()
. That method is used to compare strings for less/greater-than. To implement it, I simply place the two objects (as strings) into a list, sort the list, and then see where the first object ended up in the list. If it ended up in the first index, it's less than obj2, and if in 2nd index, it's great than obj2. To fourth parameter accounts for checking for <= or >=, and if true (and only if the desired comparison resulted in false), also checks the two objects for equality:
private bool CompareStrings(object obj1, object obj2, int index, bool orEqual)
{
bool result = false;
_strings.Clear();
_strings.Add(Convert.ToString(obj1));
_strings.Add(Convert.ToString(obj2));
_strings.Sort();
result = (Convert.ToString(obj1) == _strings[index]);
if (!result && orEqual)
{
result = CompareEqual(obj1, obj2);
}
return result;
}
Usage would go something like this:
static void Main(string[] args)
{
Evaluator eval = new Evaluator();
bool e1 = eval.Evaluate("EQUAL", "test", "text"); bool e2 = eval.Evaluate("EQUAL", 1, 1); bool e3 = eval.Evaluate("GREATER THAN", "test", "text"); bool e4 = eval.Evaluate("GREATER THAN", "zest", "text"); bool e5 = eval.Evaluate("LESS THAN", 1.675M, "david"); bool e6 = eval.Evaluate("GREATER THAN OR EQUAL", "text", "text"); bool e7 = eval.Evaluate("EQUAL", 1f, 1f); }
Final Comments
I'll be the first to admit that there may quite possibly be a more elegant way to do this, but this worked the first time, and it's reasonably fast given that we don't expect any more than a dozen or so expressions to be evaluated (and usually only between one and three), and it came to less than 180 lines of code.