- 脚本内定义函数 → 定义与调用(与C#语法一致)
- 外部注入函数 → C# 注入方法
- 获取函数 → 返回脚本内定义的函数或者外部注入的函数
- 生成Lambda → 生成 .NET Expression/Lambda
- 生成委托 → 动态脚本生成可复用委托
一、脚本内定义函数
脚本内定义函数,使得脚本配置更加灵活,不必依赖于硬编码增加函数。
标准定义
1 string s = @" 2 int sum(int a, int b) { 3 return a + b; 4 } 5 sum(3, 5); // 8 6 "; 7 var script = new Script(); 8 Assert.AreEqual(8, script.Eval(s));
简化定义(表达式体)
1 string s = @" 2 int sum(int a, int b) => a + b; 3 sum(3, 5); // 8 4 "; 5 var script = new Script(); 6 Assert.AreEqual(8, script.Eval(s));
匿名函数
函数名为“_”下划线。
1 string s = @" 2 var sum = int _(int a, int b) => a + b; 3 sum.Invoke(3, 5); // 8 4 "; 5 var script = new Script(); 6 Assert.AreEqual(8, script.Eval(s));
递归函数
1 string s = @" 2 int fib(int n) { 3 if (n <= 1) return n; 4 return fib(n - 1) + fib(n - 2); 5 } 6 fib(10); // 55 7 "; 8 var script = new Script(); 9 Assert.AreEqual(55, script.Eval(s));
函数重载
函数名相同,参数个数或参数类型不同。
1 string s = @" 2 int sum(int a, int b) => a + b; 3 int sum(int a, int b, int c) => a + b + c; 4 sum(3, 5) + sum(10, 20, 30); // 8 + 60=68 5 "; 6 var script = new Script(); 7 Assert.AreEqual(68, script.Eval(s));
二、注入外部函数
脚本上下文中注入函数
脚本上下文ScriptContext中提供了多个重载AddFunc/AddAction方法:



注入委托
1 var script = new Script(); 2 script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); 3 Assert.AreEqual(8, script.Eval("sum(3,5)"));
注入类静态方法
注入公开静态方法,不会注入泛型方法。
1 var script = new Script(); 2 script.Context.AddFunc<Math>(); 3 Assert.AreEqual(8, script.Eval("Abs(-8)")); // Math.Abs(-8)
注入类实例方法
注入公开实例方法,不会注入泛型方法。
1 var random = new System.Random(); 2 var script = new Script(); 3 // 注入所有公开实例方法 4 script.Context.AddFunc(random); 5 // 只注入Next方法,忽略其他方法 6 //script.Context.AddFunc(random, method => method.Name == "Next" ? method.Name : null); 7 Console.WriteLine(script.Eval("Next(1, 10)")); // random.Next(1, 10)
注入IFunctionEvaluator
实现IFunctionEvaluator可进行更细微的逻辑控制,非常适合实现参数数量、参数类型、返回值类型不固定的函数;同时如果实现IFunctionBuilder接口,可对函数编译进行控制,提高编译执行模式的性能。
内部的操作符就是实现IFunctionEvaluator和IFunctionBuilder来处理的。
示例:定义幂运算符**
1 /// <summary> 2 /// 幂运算:2**3=8 3 /// </summary> 4 public class PowerOperator : IFunctionEvaluator, IFunctionBuilder 5 { 6 public static readonly PowerOperator Instance = new PowerOperator(); 7 8 public void Build(FunctionBuildArgs e) 9 { 10 var left = e.Args[0].Build(e.BuildContext, e.ScriptContext, e.Options); 11 var right = e.Args[1].Build(e.BuildContext, e.ScriptContext, e.Options); 12 var v1 = Expression.Convert(left, typeof(double)); 13 var v2 = Expression.Convert(right, typeof(double)); 14 var v = Expression.Call(ExpressionUtils.Method_Math_Power, v1, v2); 15 var maxType = ScriptUtils.GetMaxType(left.Type, right.Type); 16 e.Result = Expression.Convert(v, maxType); 17 } 18 19 public void Eval(FunctionEvalArgs e) 20 { 21 if (e.Args.Count == 2) 22 { 23 var v1 = e.Args[0].Eval(e.Context, e.Options, e.Control, out _); 24 var v2 = e.Args[1].Eval(e.Context, e.Options, e.Control, out _); 25 var r = Math.Pow(Convert.ToDouble(v1), Convert.ToDouble(v2)); 26 var maxType = ScriptUtils.GetMaxType(v1.GetType(), v2.GetType()); 27 e.SetResult(ScriptUtils.Convert(r, maxType), maxType); 28 } 29 } 30 }
注入CustomFuncton
CustomFunction是由ITreeNode构造的函数,脚本中定义的函数就是解析成CustomFunction加到上下文环境中的。CustomFunction定义如下:
1 public class CustomFunction 2 { 3 public string Name { get; } 4 public string[] ArgNames { get; } 5 public Type[] ArgTypes { get; } 6 public Type ReturnType { get; } 7 public ITreeNode Body { get; } 8 9 public CustomFunction(string name, Type returnType, string[] argNames, Type[] argTypes, ITreeNode body); 10 11 public object Eval(ScriptContext context, BuildOptions options, EvalControl control, IList<ITreeNode> args, out Type returnType); 12 }
顶级上下文中注入函数
在ScriptContext.Root顶级上下文中注入函数,可实现全局注入函数,并且所有脚本语言共享。
1 // 顶级上下文中注入函数 2 ScriptContext.Root.AddFunc<int, int, int>("sum", (a, b) => a + b); 3 4 // 直接调用sum函数 5 var script = new Script(); 6 Assert.AreEqual(8, script.Eval("sum(3,5)"));
脚本语言中注入函数
只在指定脚本语言中有效,其他脚本语言无法调用。
1 // C#脚本语言中注入函数 2 CSharpLang.Instance.AddFunc<int, int, int>("sum", (a, b) => a + b); 3 4 // 默认C#语言,可以直接调用sum函数 5 var script = new Script(); 6 // 如果指定其他语言,则报错sum函数不存在 7 // script.Context.Langs = new [] { "python3" }; 8 Assert.AreEqual(8, script.Eval("sum(3,5)"));
三、获取函数
只能从ScriptContext上下文中获取函数,如果当前上下文中不存在则从上级搜索,不会搜索ScriptLang中的函数。
1 string s = @" 2 int sum(int a, int b) => a + b; 3 sum(3, 5); // 8 4 "; 5 var script = new Script(); 6 Assert.AreEqual(8, script.Eval(s)); 7 var func = script.Context.GetFunc<int, int, int>("sum"); 8 Assert.AreEqual(30, func(10,20));
四、生成Lambda
动态脚本除了执行计算结果,还可以生成Lambda,该功能可用于将脚本转化为LINQ。
示例:将动态脚本条件语句转化为LINQ查询条件
1 var script = new Script(); 2 var whereCondition = script.Lambda<Person, bool>("p.Name=='tom' || p.Name=='jim'", "p"); 3 IQueryable<Person> query = ...; 4 // query.Where(p => p.Name=="tom" || p.Name=="jim") 5 var list = query.Where(whereCondition).ToList();
五、生成委托
将脚本编译生成委托,然后在程序调用执行,也是一个常用场景。
1 var script = new Script(); 2 var sum = script.Compile<int, int, int>("a+b", "a", "b"); 3 Assert.AreEqual(8, sum(3, 5));
怎么样,有没有你想要的功能呢?欢迎一起交流学习!
AScript开源地址:https://gitee.com/rockey627/AScript
