Click here to Skip to main content
15,885,792 members
Articles / Programming Languages / C# 4.0
Tip/Trick

Ad-Hoc Expression Evaluation

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
25 Jul 2012CPOL2 min read 17.6K   24   1   12
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:
C#
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:

C#
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:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
GeneralSimplification w/ Generics Pin
Andrew Rissing25-Jul-12 8:53
Andrew Rissing25-Jul-12 8:53 
GeneralRe: Simplification w/ Generics Pin
#realJSOP25-Jul-12 10:06
mve#realJSOP25-Jul-12 10:06 
GeneralRe: Simplification w/ Generics Pin
Andrew Rissing25-Jul-12 10:19
Andrew Rissing25-Jul-12 10:19 
GeneralRe: Simplification w/ Generics Pin
#realJSOP25-Jul-12 10:31
mve#realJSOP25-Jul-12 10:31 
GeneralRe: Simplification w/ Generics Pin
Andrew Rissing25-Jul-12 10:34
Andrew Rissing25-Jul-12 10:34 
GeneralRe: Simplification w/ Generics Pin
#realJSOP25-Jul-12 11:23
mve#realJSOP25-Jul-12 11:23 
GeneralRe: Simplification w/ Generics Pin
Andrew Rissing25-Jul-12 11:32
Andrew Rissing25-Jul-12 11:32 
GeneralRe: Simplification w/ Generics Pin
#realJSOP10-Aug-12 3:36
mve#realJSOP10-Aug-12 3:36 
GeneralRe: Simplification w/ Generics Pin
Andrew Rissing10-Aug-12 4:10
Andrew Rissing10-Aug-12 4:10 
SuggestionSimplification... Pin
Andrew Rissing25-Jul-12 8:38
Andrew Rissing25-Jul-12 8:38 
SuggestionCompareStrings - CompareTo Pin
Andrew Rissing25-Jul-12 8:12
Andrew Rissing25-Jul-12 8:12 
GeneralMy vote of 5 Pin
Nagy Vilmos25-Jul-12 6:15
professionalNagy Vilmos25-Jul-12 6:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.