-
Notifications
You must be signed in to change notification settings - Fork 98
Variables and Functions
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 |
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}
From version 1.4.9.0 you can setSubExpression
variables that are evaluate as an expression when they are met.
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
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.
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
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.
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
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
To use extension methods ExpressionEvaluator need to know which classes implements extension methods. It can be done like this :
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
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"
// 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;
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));
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
.
- Getting Started
- Variables and Functions
- Operators and Keywords
- C# Types Management
- ExpandoObject
- Code Comments Management
- Advanced Customization and Hacking
- Caching
-
Options
- CultureInfoForNumberParsing
- OptionCaseSensitiveEvaluationActive
- OptionVariablesPersistenceCustomComparer
- OptionFluidPrefixingActive
- OptionForceIntegerNumbersEvaluationsAsDoubleByDefault
- OptionNumberParsingDecimalSeparator
- OptionNumberParsingThousandSeparator
- OptionFunctionArgumentsSeparator
- OptionInitializersSeparator
- OptionInlineNamespacesEvaluationRule
- OptionNewFunctionEvaluationActive
- OptionNewKeywordEvaluationActive
- OptionStaticMethodsCallActive
- OptionStaticProperiesGetActive
- OptionInstanceMethodsCallActive
- OptionInstanceProperiesGetActive
- OptionIndexingActive
- OptionStringEvaluationActive
- OptionCharEvaluationActive
- OptionEvaluateFunctionActive
- OptionVariableAssignationActive
- OptionPropertyOrFieldSetActive
- OptionIndexingAssignationActive
- OptionScriptEvaluateFunctionActive
- OptionOnNoReturnKeywordFoundInScriptAction
- OptionScriptNeedSemicolonAtTheEndOfLastExpression
- OptionAllowNonPublicMembersAccess
- Todo List