C# 表达式解释器
Supported platforms: .NET Core 3.1, .NET Core 5.0 and above, .NET 4.6.2
Dynamic Expresso is an interpreter for simple C# statements written in .NET Standard 2.0. Dynamic Expresso embeds its own parsing logic, really interprets C# statements by converting it to .NET lambda expressions or delegates.
Using Dynamic Expresso developers can create scriptable applications, execute .NET code without compilation or create dynamic linq statements.
Statements are written using a subset of C# language specifications. Global variables or parameters can be injected and used inside expressions. It doesn't generate assembly but it creates an expression tree on the fly.
For example you can evaluate math expressions:
var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");
or parse an expression with variables or parameters and invoke it multiple times:
var interpreter = new Interpreter().SetVariable("service", new ServiceExample());
string expression = "x > 4 ? service.OneMethod() : service.AnotherMethod()";
Lambda parsedExpression = interpreter.Parse(expression, new Parameter("x", typeof(int)));
var result = parsedExpression.Invoke(5);
or generate delegates and lambda expressions for LINQ queries:
var prices = new [] { 5, 8, 6, 2 };
var whereFunction = new Interpreter().ParseAsDelegate<Func<int, bool>>("arg > 5");
var count = prices.Where(whereFunction).Count();
Dynamic Expresso live demo: http://dynamic-expresso.azurewebsites.net/
Dynamic Expresso is available on [NuGet]. You can install the package using:
PM> Install-Package DynamicExpresso.Core
Source code and symbols (.pdb files) for debugging are available on [Symbol Source].
dynamic (ExpandoObject for get properties, method invocation and indexes(#142), see #72. DynamicObject for get properties and indexes, see #142)You can parse and execute void expression (without a return value) or you can return any valid .NET type. When parsing an expression you can specify the expected expression return type. For example you can write:
var target = new Interpreter();
double result = target.Eval<double>("Math.Pow(x, y) + 5",
new Parameter("x", typeof(double), 10),
new Parameter("y", typeof(double), 2));
The built-in parser can also understand the return type of any given expression so you can check if the expression returns what you expect.
Variables can be used inside expressions with Interpreter.SetVariable method:
var target = new Interpreter().SetVariable("myVar", 23);
Assert.That(target.Eval("myVar"), Is.EqualTo(23));
Variables can be primitive types or custom complex types (classes, structures, delegates, arrays, collections, ...).
Custom functions can be passed with delegate variables using Interpreter.SetFunction method:
Func<double, double, double> pow = (x, y) => Math.Pow(x, y);
var target = new Interpreter().SetFunction("pow", pow);
Assert.That(target.Eval("pow(3, 2)"), Is.EqualTo(9.0));
Custom Expression can be passed by using Interpreter.SetExpression method.
Parsed expressions can accept one or more parameters:
var interpreter = new Interpreter();
var parameters = new[] {
new Parameter("x", 23),
new Parameter("y", 7)
};
Assert.That(interpreter.Eval("x + y", parameters), Is.EqualTo(30));
Parameters can be primitive types or custom types. You can parse an expression once and invoke it multiple times with different parameter values:
var target = new Interpreter();
var parameters = new[] {
new Parameter("x", typeof(int)),
new Parameter("y", typeof(int))
};
var myFunc = target.Parse("x + y", parameters);
Assert.That(myFunc.Invoke(23, 7), Is.EqualTo(30));
Assert.That(myFunc.Invoke(32, -2), Is.EqualTo(30));
Either a variable or a parameter with name this can be referenced implicitly.
class Customer { public string Name { get; set; } }
var target = new Interpreter();
// 'this' is treated as a special identifier and can be accessed implicitly
target.SetVariable("this", new Customer { Name = "John" });
// explicit context reference via 'this' variable
Assert.That(target.Eval("this.Name"), Is.EqualTo("John"));
// 'this' variable is referenced implicitly
Assert.That(target.Eval("Name"), Is.EqualTo("John"));
Currently predefined types available are:
Object object
Boolean bool
Char char
String string
SByte Byte byte
Int16 UInt16 Int32 int UInt32 Int64 long UInt64
Single Double double Decimal decimal
DateTime TimeSpan
Guid
Math Convert
You can reference any other custom .NET type by using Interpreter.Reference method:
var target = new Interpreter().Reference(typeof(Uri));
Assert.That(target.Eval("typeof(Uri)"), Is.EqualTo(typeof(Uri)));
Assert.That(target.Eval("Uri.UriSchemeHttp"), Is.EqualTo(Uri.UriSchemeHttp));
You can use the Interpreter.ParseAsDelegate<TDelegate> method to directly parse an expression into a .NET delegate type that can be normally invoked.
In the example below I generate a Func<Customer, bool> delegate that can be used in a LINQ where expression.
…
This is the preferred way to parse an expression when the parameters it can accept and the value it must return are known at compile time.
You can use the Interpreter.ParseAsExpression<TDelegate> method to directly parse an expression into a .NET lambda expression (Expression<TDelegate>).
In the example below I generate a Expression<Func<Customer, bool>> expression that can be used in a Queryable LINQ where expression or in any other place where an expression is required. Like Entity Framework or other similar libraries.
…
Statements can be written using a subset of the C# syntax. Here you can find a list of the supported expressions:
Supported operators:
CategoryOperators Primaryx.y f(x) a[x] new typeof
Unary+ - ! (T)x
Multiplicative* / %
Additive+ -
Relational and type testing< > <= >= is as
Equality== !=
Logical AND&
Logical OR|
Logical XOR^
Conditional AND&&
Conditional OR||
Conditional?:
Assignment=
Null coalescing??
Operators precedence is respected following C# rules (Operator precedence and associativity).
Some operators, like the assignment operator, can be disabled for security reason.
true false null
Real literal suffixesd f m
Integer literal suffixesu l ul lu
String/char"" ''
The following character escape sequences are supported inside string or char literals:
\' - single quote, needed for character literals\" - double quote, needed for string literals\\ - backslash\0 - Unicode character 0\a - Alert (character 7)\b - Backspace (character 8)\f - Form feed (character 12)\n - New line (character 10)\r - Carriage return (character 13)\t - Horizontal tab (character 9)\v - Vertical quote (character 11)Any standard .NET method, field, property or constructor can be invoked.
…
var target = new Interpreter();
Assert.That(target.Eval("new DateTime(2015, 1, 24)"), Is.EqualTo(new DateTime(2015, 1, 24));
Dynamic Expresso also supports:
var x = new int[] { 10, 30, 4 };
var target = new Interpreter()
.Reference(typeof(System.Linq.Enumerable))
.SetVariable("x", x);
Assert.That(target.Eval("x.Count()"), Is.EqualTo(x.Count()));
array[0])params keyword)Dynamic Expresso has partial supports of lambda expressions. For example, you can use any Linq method:
var x = new string[] { "this", "is", "awesome" };
var options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions; // enable lambda expressions
var target = new Interpreter(options)
.SetVariable("x", x);
var results = target.Eval<IEnumerable<string>>("x.Where(str => str.Length > 5).Select(str => str.ToUpper())");
Assert.That(results, Is.EqualTo(new[] { "AWESOME" }));
Note that parsing lambda expressions is disabled by default, because it has a slight performance cost.
To enable them, you must set the InterpreterOptions.LambdaExpressions flag.
It's also possible to create a delegate directly from a lambda expression:
var options = InterpreterOptions.Default | InterpreterOptions.LambdaExpressions; // enable lambda expressions
var target = new Interpreter(options)
.SetVariable("increment", 3); // access a variable from the lambda expression
var myFunc = target.Eval<Func<int, string, string>>("(i, str) => str.ToUpper() + (i + increment)");
Assert.That(lambda.Invoke(5, "test"), Is.EqualTo("TEST8"));
By default all expressions are considered case sensitive (VARX is different than varx, as in C#).
There is an option to use a case insensitive parser. For example:
var target = new Interpreter(InterpreterOptions.DefaultCaseInsensitive);
double x = 2;
var parameters = new[] {
new Parameter("x", x.GetType(), x)
};
Assert.That(target.Eval("x", parameters), Is.EqualTo(x));
Assert.That(target.Eval("X", parameters), Is.EqualTo(x));
Sometimes you need to check which identifiers (variables, types, parameters) are used in expression before parsing it. Maybe because you
暂无开放 Issues,或尚未同步最近议题。