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
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.
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.