Skip to content

Variables and Functions

Coding Seb edited this page Feb 23, 2021 · 45 revisions

Standard constants (variables)

Constant Value Type
null C# null value N/A
true C# true value System.Boolean
false C# false value System.Boolean
Pi 3.14159265358979 System.Double
E 2.71828182845905 System.Double

Custom variables

You can define your own variables

Examples :
Live examples here

ExpressionEvaluator evaluator = new ExpressionEvaluator();
evaluator.Variables = new Dictionary<string, object>()
{
  { "x", 2.5 },
  { "y", -3.6 },
  { "myVar", "Hello World" },
  { "myArray", new object[] { 3.5, "Test", false } },
};
x+y
-1.1

myVar + " !!!"
Hello World !!!

myArray.Length
3

myArray[0]
3.5

myArray[1].Length
4

myArray[2] || true
True

A very useful functionality is that you can store callable delegates in variables :

ExpressionEvaluator evaluator = new ExpressionEvaluator();
evaluator.Variables = new Dictionary<string, object>()
{
    { "Add", new Func<int,int,int>((x, y) => x + y)},
    { "SayHelloTo", new Action<string>(name => Console.WriteLine($"Hello {name} !!!"))},
};
Add(5, 9)
14

SayHelloTo("John")
Hello John !!!
{null}

SubExpression variable

From version 1.4.9.0 you can setSubExpression variables that are evaluate as an expression when they are met.

Live examples here

ExpressionEvaluator evaluator = new ExpressionEvaluator();

evaluator.Variables["a"] = 1;
evaluator.Variables["b"] = 2;
evaluator.Variables["c"] = new SubExpression("a+b");
evaluator.Variables["d"] = new SubExpression("c+3");
evaluator.Variables["e"] = "c+3";
a
1

b
2

c
3

d
6

d-a
5

e
c+3

Context object

From version 1.4.6.0, in addition of the Variables dictionary, you can also provide a Context object from which all public properties, fields and methods are directly available.

Live example here

public class Person
{
    public string name;
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }

    public int GetTheAgeIfTodayIs(DateTime date)
    {
        var age = date.Year - BirthDate.Year;
        if (BirthDate.Date > date.AddYears(-age)) age--;
        return age;
    }
}

// ...

evaluator.Context = new Person()
{
    name = "John",
    LastName = "Smith",
    BirthDate = new DateTime(1985, 09, 11)
};
name + " " + LastName
John Smith

GetTheAgeIfTodayIs(new DateTime(2016, 5, 14))
30

Standard functions

The following functions are internally defined. (Most of these are System.Math Methods directly accessible)

Name Description Example Result
Abs(double number) Return a double that is the absolute value of number Abs(-3.2d) 3.2d
Acos(double d) Return a double value that is the angle in radian whose d is the cosine
d must be betwteen -1 and 1
Acos(-0.5d) 2.0943951023032d
Array(object obj1, object obj2 ,...) Return a array (System.Object[]) of all given arguments Array(1, "Hello", true) new object[]{1, "Hello", true}
ArrayOfType(Type typeOfTheArray, object obj1, object oj2, ...) Return a array of the specified type ArrayOfType(typeof(char), ',',';') new char[]{',', ';'}
Asin(double d) Return a double value that is the angle in radian whose d is the sine
d must be betwteen -1 and 1
Asin(-0.2d) 0.304692654015398d
Atan(double d) Return a double value that is the angle in radian whose d is the tangent Atan(2.1) 1.1263771168938d
Atan2(double x, double y) Return a double value that is the angle in radian whose the tangente is the quotient of x and y
Atan2(2.1d, 3.4d) 0.553294325322293d
Avg(double nb1, double nb2 ,...) Return a double value that is the average value of all given arguments Avg(1, 2.5, -4, 6.2) 1.425d
Ceiling(double a) Return a double value that is the smallest integer greater than or equal to the specified number. Ceiling(4.23d) 5d
Cos(double angle) Return a double value that is the cosine of the specified angle in radian Cos(2 * Pi) 1d
Cosh(double angle) Return a double value that is the hyperbolic cosine of the specified angle in radian Cosh(2d) 3.76219569108363d
Evaluate(string expression) Return the result of the evaluation of the given expression. You can disable it with OptionEvaluateFunctionActive Evaluate("1+2") 3d
Exp(double d) Return a double value that is e raised to the specified d power Exp(3d) 20.0855369231877d
Floor(double d) Return a double value that is the largest integer less than or equal to the specified d argument Floor(4.23d) 4d
IEEERemainder(double x, double y) Return a double value that is the remainder resulting from the division of x by y IEEERemainder(9, 8) 1d
in(object valueToFind, object obj1, object obj2...) Return a boolean value that indicate if the first argument is found in the other arguments in(8, 4, 2, 8) true
List(object obj1, object obj2 ,...) Return a List (System.Collections.Generic.List) of all given arguments List(1, "Hello", true) new List<object>(){1, "Hello", true}
ListOfType(Type typeOfTheList, object obj1, object oj2, ...) Return a List of the specified type ListOfType(typeof(string), "Text1","Text2") new List<string>{"Text1", "Text2"}
Log(double a, double base) Return a double value that is the logarithm of a in the specified base Log(64d, 2d) 6d
Log10(double a) Return a double value that is the base 10 logarithm of a specified a Log10(1000d) 3d
Max(double nb1, double nb2 ,...) Return a double value that is the maximum value of all given arguments Max(1d, 2.5d, -4d) 2.5d
Min(double nb1, double nb2 ,...) Return a double value that is the minimum value of all given arguments Min(1d, 2.5d, -4d) -4d
new(TypeOrClass, constructorArg1, constructorArg2 ...) Create an instance of the specified class as first argument and return it. A optional list of additional arguments can be passed as constructor arguments new(Random).next(0,10) 5d // or a random value between 1 and 9
Pow(double x, double y) Return a double value that is x elevate to the power y Pow(2,4) 16d
Round(double d, (optional) int digits, (optional) MidpointRounding mode) Rounds d to the nearest integer or specified number of decimal places. Round(2.432,1) 2.4d
ScriptEvaluate(string script) Return the result of the evaluation of the given script. You can disable it with OptionScriptEvaluateFunctionActive ScriptEvaluate("value = 1+2;\r\nif(value > 2)\r\nreturn \"OK\";\r\nelse\r\nreturn \"NOK\";") "OK"
Sign(double d) Return 1,-1 or 0 indicating the sign of d Sign(-12) -1d
Sin(double angle) Return a double value that is the sine of the specified angle in radian Sin(Pi/2) 1d
Sinh(double angle) Return a double value that is the hyperbolic sine of the specified angle in radian Sinh(2d) 3.62686040784702d
Sqrt(double d) Return a double value that is the square root of the specified d value Sqrt(4d) 2d
Tan(double angle) Return a double value that is the tangent of the specified angle in radian Tan(Pi / 4) 1d
Tanh(double angle) Return a double value that is the hyperbolic tangent of the specified angle in radian Tanh(2d) 0.964027580075817d
Truncate(double d) Return a double value that is the integer part of the specified d value Truncate(2.45d) 2d

Remark : The old if function (NCalc style) has been removed. This to avoid conflicts with the new if, else if, else keywords in script mode. To do something similar on a expression level use the conditional operator ( ? : ) instead.

On the fly variables and functions evaluation

In addition to custom variables, you can add variables and/or functions "on the fly" during evaluation.
To do so, 4 C# events are provided. They are fired at evaluation time.

2 events are fired before all evaluations of variables, fields, properties and functions and methods. These two events can be canceled so no further evaluations are done. They are always fired

2 events are fired after all "standard" evaluation of variables, fields, properties and functions and methods. These two events are only fired if no others evaluations succeed. It avoid conflicts but it has also some performance drawback

Before version 1.4.0.0 it had only 2 events (EvaluateVariable and EvaluateFunction) evaluated before but not cancelable

Remark : Can be use to define or redefine on object instances methods or properties

Live example here

ExpressionEvaluator evaluator = new ExpressionEvaluator();
// always evaluated before other var and func evaluations 
evaluator.PreEvaluateVariable += Evaluator_PreEvaluateVariable;
evaluator.PreEvaluateFunction += Evaluator_PreEvaluateFunction;

// evaluated if no existing var or func exists
evaluator.EvaluateVariable += Evaluator_EvaluateVariable;
evaluator.EvaluateFunction += Evaluator_EvaluateFunction;
//...

private static void Evaluator_PreEvaluateVariable(object sender, VariablePreEvaluationEventArg e)
{
    if (e.Name.Equals("myvar1"))
    {
        e.Value = 5;
    }
    else if(e.Name.StartsWith("P"))
    {
        e.CancelEvaluation = true;
    }
}

private static void Evaluator_PreEvaluateFunction(object sender, FunctionPreEvaluationEventArg e)
{
    if(e.Name.Equals("Test") && e.Args.Count == 1)
    {
        e.Value = $"It is a test for {e.EvaluateArg(0)}";
    }
    else if(e.Name.StartsWith("A"))
    {
        e.CancelEvaluation = true;
    }
}

private static void Evaluator_EvaluateVariable(object sender, VariableEvaluationEventArg e)
{
    if(e.Name.ToLower().Equals("myvar2"))
    {
        e.Value = 8;
    }
    else if(e.Name.Equals("MultipliedBy2") && e.This is int intValue)
    {
        e.Value = intValue * 2;
    }
    else if (e.Name.Equals("GenericAssembly") && e.HasGenericTypes)
    {
        // e.EvaluateGenericTypes() return a Type[]
        e.Value = e.EvaluateGenericTypes()[0].Assembly.GetName().Name;
    }
}

private static void Evaluator_EvaluateFunction(object sender, FunctionEvaluationEventArg e)
{
    if(e.Name.ToLower().Equals("sayhello") && e.Args.Count == 1)
    {
        e.Value = $"Hello {e.EvaluateArg(0)}";
    }
    else if(e.Name.Equals("Add") && e.This is int intValue)
    {
        e.Value = intValue + (int)e.EvaluateArg(0);
    }
    else if(e.Name.Equals("GenericNamespace") && e.HasGenericTypes)
    {
        // e.EvaluateGenericTypes() return a Type[]
        e.Value = e.EvaluateGenericTypes()[0].Namespace;
    }
}
myvar1 + 2
7

Pi
throw an exception (Pi not found)

Test(5)
It is a test for 5

Abs(-3)
throw an exception (Abs not found)

myVar2 + 2
10

SayHello("Bob")
Hello Bob

3.MultipliedBy2
6

3.Add(2)
5

GenericNamespace<List<string>>()
System.Collections.Generic

GenericAssembly<string>
mscorlib

Extension methods

To use extension methods ExpressionEvaluator need to know which classes implements extension methods. It can be done like this :

Live example here

public static class StringExtendedMethods
{
    public static string AddExtended(this string str)
    {
        return str + " extended";
    }
}

// ...

ExpressionEvaluator evaluator = new ExpressionEvaluator(new Dictionary<string, object>()
{
    { "x", "Test" }
});

evaluator.StaticTypesForExtensionsMethods.Add(typeof(StringExtendedMethods));
x.AddExtended()
Test extended

Remark :
ExpressionEvaluator do not find extension methods by itself because depending on the size of your assembly it can be very slow. But if you want, you can use the following code snippet one time somewhere in an "init" place in your code

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
// ...
foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach(Type type in assembly.GetTypes())
    {
        if(type.IsSealed
            && !type.IsGenericType
            && !type.IsNested
            && type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
                .ToList()
                .Any(method => method.IsDefined(typeof(ExtensionAttribute), false)))
        {
            evaluator.StaticTypesForExtensionsMethods.Add(type);
        }
    }
}

Remark :
By default the StaticTypesForExtensionsMethods List contains typeof(Enumerable) to allow a bunch of Linq stuff

Go fluid with a simple methods prefixing convention

When using single expression evaluation. There are cases where we need to use void methods in a fluid syntax manner.

You only need to prefix the method name with "Fluid" or "Fluent"

Live example here

// Example Add on List
List("hello", "bye").FluidAdd("test").Count
3

List("hello", "bye").Select(x => x.ToUpper()).ToList().FluentAdd("test")[0]
HELLO

List("hello", "bye").Select(x => x.ToUpper()).ToList().FluentAdd("test")[1]
BYE

List("hello", "bye").Select(x => x.ToUpper()).ToList().FluentAdd("test")[2]
test

List("hello", "bye").Select(x => x.ToUpper()).ToList().FluentAdd("test").FluidForEach(t => Console.WriteLine(t)).Count
HELLO
BYE
test
3

If needed this fonctionality can be disabled with :

evaluator.OptionFluidPrefixingActive = false;

Declare and init variables in scripts

In scripts you can declare and use variables.

The way to declare a variable from a script has evolved according of the version of ExpressionEvaluator you use.

An assignation of a variable that do not exists create it automatically. (Can contains any type)
Before version 1.4.0.0 of ExpressionEvaluator it was the only way to declare a variable.
From version 1.4.0.0 you can optionally use the keyword "var". (Can contains any type)
From version 1.4.3.0 you can declare strongly typed variable. (It limit the type that can contain a variable) or optionally use the keyword "dynamic".

// Only supported from version 1.4.3.0
int x = 2;
string text = "hello";
List<string> myList = new List<string>();

for(int i = 0; i < 10; i++)
...

// If you have an older version. Write this instead :
// Still available (for old script compatibility)
x = 2;
text = "hello";
myList = new List<string>();

for(i = 0; i < 10; i++)
...

// From version 1.4.0.0 you can also write
// Still available (for old script compatibility)
var x = 2;
var text = "hello";
var myList = new List<string>();

for(var i = 0; i < 10; i++)

Remark
All variables are injected in the evaluator.Variables dictionnary.
For strongly typed variable you can only define one time a variable with a specific name.
Warning if you use the same evaluator instance multiple times to call evaluator.ScriptEvaluate(script); with a script that declare strongly typed variables you will have an exception the second time.

Here is a small easy snippet of code to call between your calls of evaluator.ScriptEvaluate(script); to manage this specific case :

// This code remove all strongly typed variables of the Variable dictionnary.
evaluator.Variables
         .ToList()
         .FindAll(kvp => kvp.Value is StronglyTypedVariable)
         .ForEach(kvp => evaluator.Variables.Remove(kvp.Key));

Simulate function and methods declaration with lambda and multiline lambda

As lambda can be stored in variable and can be multiline in scripts you can use them to simulate function declaration.

--------------------------------------------
Add = (x, y) => x+y;

Add(3, 2);
---------------- Result --------------------
5
--------------------------------------------
SayHelloTo = (name) =>
{
    hello = "Hye";
    return $"{hello} {name}";
};

SayHelloTo("Joe");
---------------- Result --------------------
Hye Joe

Remark
You can also assign a lambda to a ExpandoObject and so call it as it was a method of this ExpandoObject.

Table Of Content

Clone this wiki locally