Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Ad-Hoc Expression Evaluation

25 Jul 2012 1  
Evaluating epressions in an ad-hoc condition.

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 we're cheking for equality as well as < or >, make an extra check
    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");                  // should be false
    bool e2 = eval.Evaluate("EQUAL", 1, 1);                            // should be true
    bool e3 = eval.Evaluate("GREATER THAN", "test", "text");           // should be false
    bool e4 = eval.Evaluate("GREATER THAN", "zest", "text");           // should be true
    bool e5 = eval.Evaluate("LESS THAN", 1.675M, "david");             // should be false (non-matching type)
    bool e6 = eval.Evaluate("GREATER THAN OR EQUAL", "text", "text");  // should be true (==)
    bool e7 = eval.Evaluate("EQUAL", 1f, 1f);                          // should be true
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here