一个精简的C#表达式执行框架Dynamic Expresso

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个精简的C#表达式执行框架Dynamic Expresso相关的知识,希望对你有一定的参考价值。

一、简介

Dynamic Expresso是一个用.NET Standard 2.0编写的简单c#语句的解释器。Dynamic Expresso嵌入了自己的解析逻辑,通过将其转换为.NET lambda表达式或委托来解释c#语句。

使用Dynamic Expresso开发人员可以创建可编写脚本的应用程序,无需编译即可执行.NET代码,或者创建动态linq语句。

语句是使用c#语言规范的子集编写的。全局变量或参数可以被注入并在表达式中使用。它不会生成程序集,但会动态地创建表达式树。

二、安装

新建控制台项目DynamicExpressoResearch,并通过NUGet添加DynamicExpresso.Core;

三、功能和特性

  1. 返回值

    你可以解析并执行一个void类型没有返回值的表达式,或者也可以返回任何有效的.NET类型。我们可以在解析表达式的时候指定期望的返回类型。

static void EvalVoidExp()
        
            var target = new Interpreter();
            var t = target.Eval("Program.empty()", new Parameter("Program", typeof(Program), new Program()));
            if (t == null)
            
                Console.WriteLine("EvalVoidExp return value is null");
            
        
        
        //EvalVoidExp return value is null
static void EvalSpecReturnTypeExp()
        
            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));

            Console.WriteLine(string.Format("We specify the return value type of the expression as double, return value is 0, value type is 1", result, result.GetType().FullName));

            int r = target.Eval<int>("Math.Pow(x, y) + 5",
                                new Parameter("x", typeof(int), 10),
                                new Parameter("y", typeof(int), 2));
            Console.WriteLine(string.Format("We specify the return value type of the expression as int, return value is 0, value type is 1", r, r.GetType().FullName));
        
        
        
        //We specify the return value type of the expression as double, return value is 105, value type is System.Double

        //We specify the return value type of the expression as int, return value is 105, value type is System.Int32
同时内置的表达式parser也可以自动感知表达式的返回类型;
static void AutoInferDataType()
        
            var target = new Interpreter();
            object r = target.Eval("Math.Pow(x, y) + 5",
                                new Parameter("x", typeof(int), 10),
                                new Parameter("y", typeof(int), 2));
            Console.WriteLine(string.Format("We do not  specify the return value type of the expression, return value is 0, value type is 1", r, r.GetType().FullName));
        

2.变量(Variables)

变量依附在Interpreter上,相当于一个全局的控制参数,可以应用到同一个Interpreter实例的所有表达式中;
Interpreter提供了不同的方法可以实现传入不同类型的变量;
SetVariable可以内置的原始类型及复杂的自定义数据类型;
static void SetParameter()
        
            var target = new Interpreter();
            target.SetVariable("rate", 0.8);
            object r = target.Eval("price * rate",
                                new Parameter("price", typeof(int), 200));
            Console.WriteLine(string.Format("The price of 200 with a 20% discount is 0", r));

            r = target.Eval("price * rate",
                                new Parameter("price", typeof(int), 499));
            Console.WriteLine(string.Format("The price of 499 with a 20% discount is 0", r));
        
        
        The price of 200 with a 20% discount is 160
        The price of 499 with a 20% discount is 399.2
SetFunction可以通过委托的方式传递函数
static void SetFunction()
        
            var target = new Interpreter();
            Func<double, double, double> pow = (x, y) => Math.Pow(x, y);
            target.SetFunction("pow", pow);
            object r = target.Eval("pow(x, y)",
                                new Parameter("x", typeof(int), 10),
                                new Parameter("y", typeof(int), 2));
            Console.WriteLine(string.Format("10 to the second power  is 0", r));

            r = target.Eval("pow(x, y)",
                                new Parameter("x", typeof(int), 2),
                                new Parameter("y", typeof(int), 4));
            Console.WriteLine(string.Format("2 to the fourth power is 0", r));
        
        
        //10 to the second power  is 100
        //2 to the fourth power is 16
SetExpression可以设置自定义的Expression
static void SetExpression()
        
            var target = new Interpreter();
            var rateExp = Expression.Constant(0.8);
            target.SetExpression("rate", rateExp);
            object r = target.Eval("price * rate",
                                new Parameter("price", typeof(int), 200));
            Console.WriteLine(string.Format("The price of 200 with a 20% discount is 0", r));

            r = target.Eval("price * rate",
                                new Parameter("price", typeof(int), 499));
            Console.WriteLine(string.Format("The price of 499 with a 20% discount is 0", r));
        

3.参数(Parameters)

参数需要每次执行的时候传递,参数可以是任意的类型,我们可以只解析一次表达式,并通过不同的参数进行多次调用;
static void Invoke()
        
            var target = new Interpreter();

            var parameters = new[] 
                 new Parameter("x", typeof(int)),
                 new Parameter("y", typeof(int))
            ;

            var myFunc = target.Parse("x + y", parameters);
            myFunc.Invoke(23, 7);
            myFunc.Invoke(32, -2);            
        

4.内置类型和自定义类型

默认内部直接支持的数据类型有
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
我们可以直接使用Interpreter.Reference来引用任何的.net类型
static void ReferenceType()
        
            var target = new Interpreter().Reference(typeof(Uri));
            Console.WriteLine((target.Eval("typeof(Uri)") as Type).FullName);
            Console.WriteLine(target.Eval("Uri.UriSchemeHttp"));
        
        
        //System.Uri
        //http

5.生成动态委托

我们可以直接使用Interpreter.ParseAsDelegate<TDelegate>来解析表达式生成对应的委托,然后可以直接调用对应的委托。
class Customer
        
            public string Name  get; set; 
            public int Age  get; set; 
            public char Gender  get; set; 
        

        static void DynamicDelegate()
        
            var customers = new List<Customer> 
                new Customer()  Name = "David", Age = 31, Gender = 'M' ,
                new Customer()  Name = "Mary", Age = 29, Gender = 'F' ,
                new Customer()  Name = "Jack", Age = 2, Gender = 'M' ,
                new Customer()  Name = "Marta", Age = 1, Gender = 'F' ,
                new Customer()  Name = "Moses", Age = 120, Gender = 'M' ,
            ;

            string whereExpression = "customer.Age > 18 && customer.Gender == 'F' && index >=0";

            var interpreter = new Interpreter();
            Func<Customer, int, bool> dynamicWhere = interpreter.ParseAsDelegate<Func<Customer, int, bool>>(whereExpression, "customer", "index");
            Console.WriteLine(customers.Where(dynamicWhere).Count());
        

6.生成lambda表达式

我们可以使用Interpreter.ParseAsExpression<TDelegate>解释表达式直接生成lambda表达式;
static void DynamicLambdaExpress()
        
            var customers = new List<Customer> 
                new Customer()  Name = "David", Age = 31, Gender = 'M' ,
                new Customer()  Name = "Mary", Age = 29, Gender = 'F' ,
                new Customer()  Name = "Jack", Age = 2, Gender = 'M' ,
                new Customer()  Name = "Marta", Age = 1, Gender = 'F' ,
                new Customer()  Name = "Moses", Age = 120, Gender = 'M' ,
            .AsQueryable();

            string whereExpression = "customer.Age > 18 && customer.Gender == 'F' && index >=0";

            var interpreter = new Interpreter();
            Expression<Func<Customer, int, bool>> dynamicWhere = interpreter.ParseAsExpression<Func<Customer, int, bool>>(whereExpression, "customer", "index");
            Console.WriteLine(customers.Where(dynamicWhere).Count());
        

7.支持的操作符

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??

8.文本标识

CategoryOperators
Constantstrue false null
Real literal suffixesd f m
Integer literal suffixesu l ul lu
String/char"" ''

字符串或者字符类型中支持的转译

  • ' - 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)

9.调用对象成员

可以在表达式中直接调用对象实例的成员
public class Student
        
            public string Name  get; set; 
            public int Age  get; set; 

            public void Hello()
            
                Console.WriteLine(string.Format("hello, my name is 0, my age is 1", Name, Age));
            

            public static Student Instance()
            
                return new Student()  Name="auto", Age = 0;
            
        

        static void InvokTypeMember()
        
            var s = new Student()  Name="mango", Age = 30;
            var target = new Interpreter().SetVariable("s", s);
            target.Reference(typeof(Student));
            Console.WriteLine(target.Eval("s.Hello()"));
            Console.WriteLine(target.Eval("s.Name"));
            Console.WriteLine(target.Eval("new Student().Hello()"));
            Console.WriteLine(target.Eval("Student.Instance().Hello()"));
        
同时可以支持扩展方法的调用;
支持数组索引的调用;
支持params数组;
部分支持泛型;
static void InvokCollectionMember()
        
            var x = new int[]  10, 30, 4 ;          
            var target = new Interpreter();
            target.SetVariable("x", x);
            Console.WriteLine(target.Eval("x.Count()"));
            //Console.WriteLine(target.Eval("x.Count<int>()"));
            Console.WriteLine(target.Eval("x[0]"));
            var s = new string[] "mango", "is","test","params" ;
            target.SetVariable("s", s);
            Console.WriteLine(target.Eval("string.Join(\\" \\", s)"));
        

10.Lambda表达式

Dynamic Expresso只支持lambda表达式的部分功能;为了减少对性能的影响,默认是不开启Lambda表达式的解析的;我们可以通过InterpreterOptions.LambdaExpressions 来启用这个功能;
static void EvalAsLambda()
        
            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())");
            Console.WriteLine(results.First());
        

11.大小写

默认情况下表达式是区分大小写的,可以通过InterpreterOptions.CaseInsensitive或者InterpreterOptions.DefaultCaseInsensitive来忽略大小写;
static void IgnorCase()
        
            var target = new Interpreter(InterpreterOptions.CaseInsensitive);

            double x = 2;
            var parameters = new[] 
                new Parameter("x", x.GetType(), x)
            ;

            Console.WriteLine(target.Eval("x", parameters));
            Console.WriteLine(target.Eval("X", parameters));
        

12.标识符探测

Dynamic Expresso支持使用Interpreter.DetectIdentifiers来获取表达式中的变量标识符。
static void DetectIdentifier()
        
            var target = new Interpreter();
            var detectedIdentifiers = target.DetectIdentifiers("x + y");

            Console.WriteLine(detectedIdentifiers.UnknownIdentifiers.First());
            Console.WriteLine(detectedIdentifiers.UnknownIdentifiers.Last());
        

13.默认数字类型

默认情况下数字会被解析成int类型或者double类型;但是在有些情况下,例如财务中需要使用decimal类型的数字;我们可以使用Interpreter.SetDefaultNumberType来设置;
static void SetNumberType()
        
            var target = new Interpreter();
            target.SetDefaultNumberType(DefaultNumberType.Decimal);
            Console.WriteLine(target.Eval("45").GetType().FullName);
            Console.WriteLine(target.Eval("10/3"));
        

14.异常

提供了ParseException作为解析表达式异常的基类,同时提供了数个不同的具体的异常类,例如UnknownIdentifierException, NoApplicableMethodException;

15.多线程

Interpreter类可以在多线程中使用,但是需要确保在多线程中只能调用get属性和Parse及Eval这些方法,而对于初始化的一些设置(SetVariable/Reference)等只能在实例化对象的阶段调用;

如果我们需要使用不同的参数多次执行表达式,最好的方式就是解析一次表达式,并调用多次解析的结果;

16.安全性

表达式中只能访问Reference中设置的类型以及设置的变量和参数的对象实例;我们需要慎重考虑暴露哪些类给用户;

从1.3版本其默认禁用直接调用除了Type.Name的反射类方法;

17.功能局限

Not every C# syntaxes are supported. Here some examples of NOT supported features:

  • Multiline expressions

  • for/foreach/while/do operators

  • Array/list/dictionary initialization

  • Explicit generic invocation (like method

    (arg))
  • Lambda/delegate declaration (delegate and lamda are only supported as variables or parameters or as a return type of the expression)

  • Array/list/dictionary element assignment (set indexer operator)

  • Other operations on dynamic objects (only property, method invocation and index now are supported)

18.使用场景

  • Programmable applications

  • Allow the user to inject customizable rules and logic without recompiling

  • Evaluate dynamic functions or commands

  • LINQ dynamic query

以上是关于一个精简的C#表达式执行框架Dynamic Expresso的主要内容,如果未能解决你的问题,请参考以下文章

c#表达式树最完善的表达式树Expression.Dynamic的玩法

python超精简博客园爬虫(果然比C#好用的多)

8.22for循环随笔

C#用dynamic一行代码实现反射操作

表达式树中的 C# 4“动态”

.net 4.0 中的特性总结:dynamic