Click here to Skip to main content
15,881,173 members
Articles / Web Development / ASP.NET
Article

An Extensible Expression Evaluation Package (EEEP!)

Rate me:
Please Sign up or sign in to vote.
3.86/5 (17 votes)
25 Apr 20046 min read 83.2K   952   35   12
Provides runtime evaluation of mathematical and string-based expressions.

Sample Image - eeep.png

Introduction

Run-time compilation and execution is a desirable language feature; it's part of the Little Language concept, often used as a flexible control method for systems and applications. Even more basic and universal, though, is the need for simple evaluation of mathematical and string expressions.

Many scripting languages, including Perl, JScript/JavaScript, VBScript, and Transact-SQL, include a function (often called "eval" or "evaluate", EXEC(UTE) in the case of T-SQL) to evaluate and return the result from a string expression. (These expressions can also sometimes execute as normal lines of code. For instance, in JavaScript, you can dynamically create code that is interpreted on-the-fly to affect page elements; however, that is beyond the scope of this article.)

It should come as no surprise that this is a commonly-requested feature in .NET and other modern programming platforms, like Java 2. Java programmers are often forced to resort to command-line compilation of dynamically-generated code to get this effect, which is bad for performance. .NET grants us the ability to dynamically evaluate expressions via ADO.NET, and also offers a convenient in-memory compilation feature via the CodeDom namespace.

About the Project

I've sometimes used SQL Server and other databases just for their expression-evaluation capabilities, but that isn't the best use of a database server. Also, Transact-SQL is fine for simple mathematical and text-processing tasks, but the functionality built into JScript is much richer. I began with the design goal of providing reusable evaluation capability for expressions in Transact-SQL, JScript.NET, and VBScript, but eventually wound up scrapping the attempt at a VBScript implementation.

I created an extensible set of classes to encapsulate the two main dynamic evaluation abilities in .NET. The design mostly hinges around the IEvaluator interface; this interface is partially implemented at a base level by the class Evaluator, which is extended by the classes ADOEvaluator and JScriptEvaluator. My main aim was simply to create useful code that would be easy for even beginner-level programmers to use; I also dabbled in .NET reflection for the first time and learned about a couple of ADO features along the way.

All of the code, together with a VS .NET 2003 project file and a help file generated by the use of NDoc, is in the .zip file downloadable at the top of this page. In order to import the code into your own project, you'll need to reference System.Data to use ADOEvaluator, and Microsoft.JScript to use JScriptEvaluator.

The main innovation in this project is in providing a simplified interface to unlimited expression-evaluation implementations, while providing support for the {0} {1} style of passing parameters that's popular in the .NET API. It also serves as a basic example of the Facade pattern.

The API

The Evaluator superclass contains two important methods: IsValid(), which tests an expression for validity, and Evaluate, which evaluates the expression and returns a value. Both methods are overloaded to allow for up to ten replaceable arguments, together with a version that just accepts the expression itself. If the methods with extra arguments are chosen, parameter replacement is performed similar to that done by String.Format, with the first parameter value replacing {0} in the passed expression, the second replacing {1}, etc. If necessary, the prefix and suffix for these indexed values can be changed through the IEvaluator's ArgumentPrefix and ArgumentSuffix properties.

The IsSynchronized property can be used to control the locking behavior of each Evaluator subclass. By default, this is set to false, but synchronization support is provided in the superclass to make it easy to safely wrap and use non-thread-safe code in a multithreaded way. If a new implementation makes use of the Evaluator base class, the only abstract method required (or available) to be implemented is the EvaluateImpl() method, which takes a single string parameter.

The very simple ADOEvaluator class wraps a DataTable, in order to make calls to the table's Compute method. I examined this class using ildasm.exe, and what's actually going on under the covers is the instantiation and evaluation of a System.Data.DataExpression object; unfortunately, this class is internal, and therefore can't be directly used. It seems strange, but presumably the designers of .NET felt that it didn't directly have anything to do with database access, and would only clutter the ADO.NET API.

I originally wrote a version of the JScriptEvaluator class that compiled a JScript.NET class on the fly during static initialization, with the single purpose of exposing the JScript.NET eval function as a class method. It uses a randomized class name each time, to make sure that there's effectively no chance of naming collision with another class, even by design. That was before I found the Microsoft.JScript.Eval.JScriptEvaluate() method, though. Removing the need to call the generated class' method via reflection had a greatly beneficial effect on the performance of the code.

Sample Code

The code speaks for itself. You create an IEvaluator instance of the desired type, either your own implementation or ADOEvaluator or JScriptEvaluator. Next, you call Evaluate, and cast the returned object to the expected return type. I prefer to use JScript.NET over ADO.NET for performance reasons, and you probably will too.

Example #1: A simple mathematical expression

C#
using Expressions;
// ...
IEvaluator evaluator = new JScriptEvaluator();
Console.WriteLine(evaluator.Evaluate("35 * (100 + 9)"));

Output:

3815

Example #2: Replacing values, à la String.Format()

Here the placeholders {0}, {1}, and {2} are replaced by the optional parameters. This example also shows the use of the IsValid() method without any optional parameters.

C#
using Expressions;
// ...
IEvaluator evaluator = new JScriptEvaluator();
Console.WriteLine(evaluator.Evaluate("{0} * ({1} + {2})", 35, 100, 9));
Console.WriteLine(evaluator.IsValid("bling bling"));

Output:

3815
False

The Test Application

The test application source code occurs in a single file, Test.cs. This command-line application allows you to choose which of the two default implementations you'd like to use to evaluate a statement; it also allows the inclusion of a simple performance test to give you a rough idea of how long a particular statement might take to evaluate under either ADO.NET or JScript.NET. Below is a sample output of the application.

Note that for the simple mathematical expression used in both tests, JScript.NET evaluated the statement over seven times as fast as ADO.NET in this informal test on my P4 laptop. Also note that 20 microseconds is not that speedy for such a simple calculation, a strong indication that this technique should not be overused if performance is a concern. The testing method is simple: the expression is evaluated 1,000 times in a tight loop, then the number of elapsed nanoseconds is divided by 1,000 to get the rough time required for a single evaluation.

##########################################################
#                                                        #
#      EXPRESSION EVALUATION TEST APPLICATION, v1.0      #
#                                                        #
#      No warranties, either express or implied,         #
#      are made as to the fitness of this                #
#      application for the purpose of turning            #
#      aside rampaging hordes of wombats.  This is       #
#      no laughing matter... stop laughing.              #
#                                                        #
##########################################################


(E)valuate or (Q)uit?                  e
Evaluation type: (A)DO or (J)Script?   a
Performance test: (Y)es or (N)o?       y
Expression:                            (1.789 * 154) / .32

RESULTS
-------
Expression                 :  (1.789 * 154) / .32
Nanoseconds per evaluation :  150216
Evaluation result          :  860.95625


(E)valuate or (Q)uit?                  e
Evaluation type: (A)DO or (J)Script?   j
Performance test: (Y)es or (N)o?       y
Expression:                            (1.789 * 154) / .32

RESULTS
-------
Expression                 :  (1.789 * 154) / .32
Nanoseconds per evaluation :  20001.38
Evaluation result          :  860.95625


(E)valuate or (Q)uit?                  q

Credits

Thanks go to Yaron K. for first bringing the DataTable.Compute() method to my attention, and Heath Stewart for giving his usual useful four cents' worth.

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


Written By
Technical Lead
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralLooks like this one is redundant Pin
zhanglindezz21-Nov-07 15:00
zhanglindezz21-Nov-07 15:00 
for(int x = 0; x < 1000; x++) {
evaluator.Evaluate(expression);
}

I find it in Test.cs. I think maybe you wanna let us see the affect of Time interval.Big Grin | :-D
QuestionWhat about boolean expressions? Pin
bernardoh28-Jun-05 16:26
bernardoh28-Jun-05 16:26 
GeneralMessage Closed Pin
7-Feb-05 20:24
Anonymous7-Feb-05 20:24 
GeneralA more advanced expression processor Pin
Nick Pirocanac30-Aug-04 5:11
Nick Pirocanac30-Aug-04 5:11 
Generalcrap article Pin
Anonymous17-May-04 8:18
Anonymous17-May-04 8:18 
GeneralRe: crap article Pin
Jeff Varszegi17-May-04 8:32
professionalJeff Varszegi17-May-04 8:32 
GeneralIt is done much easier with JScript-based assembly. Pin
onc6-May-04 11:10
onc6-May-04 11:10 
GeneralRe: It is done much easier with JScript-based assembly. Pin
Jeff Varszegi6-May-04 12:00
professionalJeff Varszegi6-May-04 12:00 
GeneralRe: It is done much easier with JScript-based assembly. Pin
User 19428917-Oct-05 4:03
User 19428917-Oct-05 4:03 
Generalfrink Pin
No Brayn R5-May-04 2:03
No Brayn R5-May-04 2:03 
GeneralSuggestion Pin
Heath Stewart4-May-04 8:56
protectorHeath Stewart4-May-04 8:56 
GeneralRe: Suggestion Pin
Jeff Varszegi4-May-04 10:06
professionalJeff Varszegi4-May-04 10:06 

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.