Dynamic Expresso--一個精簡的C#表達式執行框架


一、簡介

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.支持的操作符

Category Operators
Primary x.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.文本標識

Category Operators
Constants true false null
Real literal suffixes d f m
Integer literal suffixes u 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM