This project has moved. For the latest updates, please go here.

Usage

At it's simplest, ExpressionEvaluator allows you to specify a C# expression in a string and evaluate it.

            var expression = new CompiledExpression("1 + 2");
            var result = expression.Eval();
            Console.WriteLine(result);  // displays 3

Using the non-generic class will give you results of the evaluated expression cast to the type object. This is great for quick use, or for where you don't know the exact output type of your expression.

Internally, ExpressionEvaluator parses your expression and generates a LINQ Expression tree. This Expression tree is then compiled into a delegate. Calling Eval() caches this delegate locally to the CompiledExpression instance so that succeeding calls to Eval() will not trigger a costly parse+recompile.

The CompiledExpression class has a StringToParse property that can be set any time after instantiation. Setting this property has the effect of clearing any function cached by Eval(), and so Eval() will trigger a parse+compile of the new expression when it is called.

The Expression property contains the Expression tree which encapsulates the parsed expression as a code structure. This can be used to analyze the expression.

Tto avoid the costly boxing, use the generic CompiledExpression<T> class. This compiles the expression as a function that returns type T.

Numeric and String Literals

The typing of numeric literals follows C# standards. You may use suffixes such as d, f, m to explicitly specify the numeric type. If no suffix is given, the default type of a numeric literal is int.

Strings deviate from C# standards to avoid messy escape sequences when embedding code inside a string. Instead of double quotes, string literals are using single quotes. Unfortunately this precludes the use of char literals for now. Strings can still be written using double quotes, which may be useful when code is stored in a medium that does not require escaping.

Accessing External Types and Instances

The parser engine does not know of any other types but intrinsic ones and anything that is registered. The following types are registered by default:

object/Object
bool/Boolean
byte/Byte
char/Char
short/Int16
int/Int32
long/Int64
ushort/UInt16
uint/UInt32
ulong/UInt64
decimal/Decimal
double/Double
float/Single
string/String

This allows you to call static methods on these types from within your expression, such as:

            var expression = new CompiledExpression("float.Parse('3.141592654')");
            var result = expression.Eval();
            Console.WriteLine(result);  // result is of type float

You may also register any other types:

The TypeRegistry class contains the methods for registering types and symbols. Set the TypeRegistry property of the CompiledExpression instance anytime before the expression is parsed (i.e. before Eval or any of the Compile methods are called)

            var registry = new TypeRegistry();
            registry.RegisterType(typeof(Guid));  // registers the symbol 'Guid' as type Guid
            registry.RegisterType("q", typeof("Math"));  // registers the symbol 'q' as type Math
            registry.RegisterSymbol("m", myclass);  // registers the symbol 'm' with instance myClass

            var expression = new CompiledExpression("Guid.NewGuid()") { TypeRegistry = registry };

One benefit of having the TypeRegistry external to the CompiledExpression is that the TypeRegistry instance can now be shared between CompiledExpression instances.

Sample Code

The following code registers the instance v of class Vars with the alias vars in the expression. Parse and Compile are called separately in order to allow benchmarking. If you are interested only in the result of eval the this is not necessary as Eval calls Compile and Compile calls Parse if neither has been previously called or of the cache has been cleared.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExpressionEvaluator;

namespace EvalTest
{
    class Program
    {
        class Vars
        {
            public DateTime myDateVar { get; set; }
            public double myDoubleVar { get; set; }
        }

        static void DoTest()
        {
            var v = new Vars() { myDoubleVar = 1234.5678 };
            string parsestr = "(vars.myDoubleVar / 4 * 3) + (vars.myDoubleVar / (2 + 5))";
            var reg = new TypeRegistry();
            reg.RegisterType("vars", v)
            var p = new CompiledExpression(parsestr) { TypeRegistry = reg };
            p.Parse();
            p.Compile();
            Console.WriteLine("Result: {0}", p.Eval());
        }

        static void Main(string[] args)
        {
            DoTest();
            Console.ReadLine();
        }
    }
}

Calling void-returning expressions

Eval() and ScopeCompile() compile the expression into a Func<> delegate, which expects a return value. Expressions that return void such as code blocks will throw an exception if compiled as a Func<>.

If a void-returning expression is encountered, Expression Evaluator will wrap the code in a code block that returns null. (Should return the default of the type?)

Alternatively the Call() and ScopeCompileCall() methods assume that the expression returns void and compiles the expression into an Action<> delgate.

    public class MyClass
    {
        public int X { get; set; }
        public void Foo()
        {
            X++;
        }

        public void Foo(int value)
        {
            X += value;
        }

        public int Bar(int value)
        {
            return value * 2;
        }
    }

/// Main program

            var data = new MyClass();

            var c1 = new CompiledExpression() { StringToParse = "X = 99" };
            var f1 = c1.ScopeCompileCall<MyClass>();
            Console.WriteLine(data.X);
            f1(data);
            Console.WriteLine(data.X);

            var reg = new TypeRegistry();
            reg.RegisterSymbol("data", data);
            var c1a = new CompiledExpression() { StringToParse = "data.X = 100", TypeRegistry = reg };
            Console.WriteLine(data.X);
            c1a.Call();
            Console.WriteLine(data.X);

            var c2 = new CompiledExpression() { StringToParse = "data.Foo()" };
            var f2 = c2.ScopeCompileCall();
            Console.WriteLine(scope.data.X);
            f2(scope);
            Console.WriteLine(scope.data.X);

Compiling a block of code

As of version 2.0.0.2, you may now compile several statements into one function using the ExpressionType property of the CompiledExpression object.

Suppose you had the following set of code:

if(a.x == 1) 
   a.y = 2; 
else { 
   a.y = 3; 
} 

a.z = a.y;

This could be store as-is in a text file or some other location retaining all the newlines and carriage returns. For illustration purposes, we have flattened the code to fit in a single string.

To compile multiple statements, simple set the ExpressionType property to CompiledExpressionType.StatementList before any of the Eval or Compile methods are called.


var a = new ClassA() { x = 1 };
var t = new TypeRegistry();
t.RegisterSymbol("a", a);
var p = new CompiledExpression { StringToParse = "if(a.x == 1) a.y = 2; else { a.y = 3; } a.z = a.y;", TypeRegistry = t };
p.ExpressionType = CompiledExpressionType.StatementList;
var f = p.Eval();
Assert.AreEqual(a.y, 2);
Assert.AreEqual(a.y, a.z);

Last edited Feb 3, 2015 at 11:08 PM by RupertAvery, version 22

Comments

No comments yet.