C#脚本引擎之AScript与Z.Expressions.Eval对比
一 简介
1.1 AScript
AScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存。
目前官方最新版本:1.7.2
开源地址:
相关文章:
1.2 Z.Expressions.Eval
Z.Expression.Eval是一个非常强大的工具,使得开发人员可以动态编译和执行C#代码和表达式。
- Execute C# Code
- Compile C# Code
- LINQ Dynamic
目前官方最新版本:6.3.13
官网地址:https://eval-expression.net
二 功能
2.1 编译缓存
二者皆支持编译缓存功能:
1 // AScript默认不启用缓存 2 var script = new Script(); 3 var result = script.Eval("5 + 6", -1); 4 5 // Z.Expressions.Eval默认启用缓存 6 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 7 var result = Z.Expressions.Eval.Execute<int>("5 + 6");
同时AScript支持缓存版本控制和定时清理缓存功能:
1 /// <summary> 2 /// 计算表达式,返回结果 3 /// </summary> 4 /// <param name="expression">脚本</param> 5 /// <param name="cacheTime"> 6 /// <para>缓存时长</para> 7 /// <para>为0表示不使用缓存(默认);</para> 8 /// <para>-1表示永久缓存;</para> 9 /// <para>大于0表示缓存时长(单位:毫秒)</para> 10 /// </param> 11 /// <param name="cacheKey"> 12 /// 缓存key(如果为空则取表达式字符串) 13 /// </param> 14 /// <param name="cacheVersion"> 15 /// 缓存版本号,如果版本号有变化,则重新编译并缓存 16 /// </param> 17 /// <returns></returns> 18 public object Eval(string expression, int cacheTime = 0, string cacheKey = null, string cacheVersion = null); 19 public object Eval(Stream expression, int cacheTime = 0, string cacheKey = null, string cacheVersion = null); 20 public object Eval(Func<string> expression, int cacheTime = 0, string cacheKey = null, string cacheVersion = null); 21 public object Eval(Func<Stream> expression, int cacheTime = 0, string cacheKey = null, string cacheVersion = null); 22 public object EvalFile(string path, int cacheTime = 0, string cacheKey = null, string cacheVersion = null);
场景一
脚本保存在数据库或其他远程端,以业务标识为cacheKey,以修改时间戳为cacheVersion:
1 string cacheKey = "xxx"; 2 string cacheVersion = "当前版本"; 3 var script = new Script(); 4 var result = script.Eval(()=>获取脚本, cacheTime: -1, cacheKey, cacheVersion);
如果版本未变化,则直接执行缓存中的委托;
如果版本有变化,则重新获取脚本-->编译-->缓存-->执行。
场景二
脚本保存在文件中,可以调用EvalFile方法,默认cacheKey为文件完整路径,cacheVersion可以设置为修改时间:
1 string cacheVersion = System.IO.File.GetLastWriteTimeUtc(path).Ticks.ToString(); 2 var script = new Script(); 3 var result = script.EvalFile(path, cacheTime: -1, cacheVersion: cacheVersion);
如果文件未修改,则直接执行缓存中的委托;
如果文件有修改,则重新读取文件-->编译-->缓存-->执行。
2.2 Compile
将脚本编译生成委托:
1 // AScript 2 var script = new Script(); 3 Func<int, int> func = script.Compile<int, int>("a + 5", "a"); 4 Func<int, int> func = script.Compile<Func<int, int>>("a + 5", new[] { "a" }); 5 6 // Z.Expressions.Eval 7 Func<int, int> func = Z.Expressions.Eval.Compile<Func<int, int>>("a + 5", "a");
2.3 注入函数
- 添加类中的方法
1 // AScript 2 var script = new Script(); 3 // 添加静态方法,支持扩展方法 4 script.Context.AddFunc(typeof(MyClass)); 5 // 添加实例方法 6 script.Context.AddFunc(new MyClass()); 7 script.Eval("Sum(5, 6)"); 8 script.Eval("Hello('tom')"); 9 10 // Z.Expressions.Eval:支持静态方法,不支持实例方法 11 Z.Expressions.EvalManager.DefaultContext.RegisterStaticMethod(typeof(MyClass)); 12 Z.Expressions.Eval.Execute("Sum(5, 6)"); 13 14 public class MyClass 15 { 16 public static int Sum(int a, int b) => a + b; 17 18 public string Hello(string name) => $"hello {name}"; 19 }
- 添加委托
1 // AScript 2 var script = new Script(); 3 script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); 4 script.Eval("sum(5, 6)"); 5 6 // Z.Expressions.Eval不支持添加委托
2.4 递归函数
AScript支持定义递归函数,Z.Expressions.Eval不支持递归函数。
1 string code = @" 2 int fib(int n) { 3 if (n <= 1) return n; 4 return fib(n - 1) + fib(n - 2); 5 } 6 fib(10) 7 "; 8 var script = new Script(); 9 var result = script.Eval<int>(code); // 55
相关文章:AScript之编译递归函数
2.5 Queryable/Enumerable扩展方法
1 string code = @" 2 var list = new List<int>() { 1, 2, 3, 4 }; 3 return list.Where(x => x > 2).ToList(); 4 "; 5 6 // AScript 7 var script = new Script(); 8 var result = script.Eval<List<int>>(code); 9 10 // Z.Expressions.Eval 11 var result = Z.Expressions.Eval.Execute<List<int>>(code);
2.6 LINQ Dynamic
1 List<int> list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 2 // AScript动态LINQ扩展 3 var result = list.WhereScript(x => "x % 2 == 0").ToList(); 4 // Z.Expressions.Eval动态LINQ扩展 5 var result = list.WhereDynamic(x => "x % 2 == 0").ToList();
2.7 LINQ查询语法
AScript支持查询语法,Z.Expressions.Eval不支持。
1 string code = @" 2 var list = new List<int>() { 1, 2, 3, 4 }; 3 var q = from item in list 4 where item > 2 5 select item * 10; 6 return q.ToList(); 7 "; 8 var script = new Script(); 9 var result = script.Eval<List<int>>(code);
2.8 混合多语言脚本
AScript脚本中使用@lang/@end指定语言:
1 Script.Langs.Set("js", JavaScriptLang.Instance); 2 Script.Langs.Set("lua", LuaLang.Instance); 3 4 string code = @" 5 int a = 12; 6 string s = 'hello'; 7 8 // 调用lua语言中的打包/解包方法 9 @lang lua 10 local data = string.pack('<i4c10', a, s) 11 local a2,s2 = string.unpack('i4c10', data) 12 @end 13 14 Console.WriteLine(a2 + s2); // 12hello 15 16 // 调用js语言中的控制台输出 17 @lang js 18 console.log(a2, s2); // 12,hello 19 @end 20 "; 21 var script = new Script(); 22 script.Eval(code);
2.9 层级上下文
- Z.Expressions.Eval中的EvalContext不支持层级,创建EvalContext实例成本高
- AScript中的ScriptContext支持层级,ScriptContext.Root为全局顶级上下文

1 // 创建业务上下文:context.Parent为ScriptContext.Root 2 var context = new ScriptContext(); 3 // TODO: context中设置共享业务数据 4 context.SetVar("order", order); 5 6 // 创建子上下文1:subContext1.Parent为context 7 var subContext1 = new ScriptContext(context); 8 // TODO: subContext1中设置子业务数据 9 subContext1.SetVar("user", user1); 10 // 执行脚本 11 var script1 = new Script(subContext1); 12 script1.Eval(code1); 13 14 // 创建子上下文2:subContext2.Parent为context 15 var subContext2 = new ScriptContext(context); 16 // TODO: subContext2中设置子业务数据 17 subContext2.SetVar("user", user2); 18 // 执行脚本 19 var script2 = new Script(subContext2); 20 script2.Eval(code2);
三 性能测试
使用Benchmark工具进行性能基准测试,测试环境:
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2) AMD Ryzen 7 7840H with Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores .NET SDK 9.0.316 [Host] : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2 DefaultJob : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2
安装nuget包:
install-package AScript install-package Z.Expressions.Eval
3.1 常量表达式
1 private static readonly string s = "100 * (5 + 5) * (6-2)"; 2 private static readonly int r = 100 * (5 + 5) * (6 - 2); 3 4 // 解释执行 5 [Benchmark] 6 public void AScript1() 7 { 8 var script = new Script(); 9 var result = script.Eval<int>(s); 10 if (result != r) throw new Exception("result error"); 11 } 12 13 // 编译执行 14 [Benchmark] 15 public void AScript2_Compile() 16 { 17 var script = new Script(); 18 var result = script.Eval<int>(s, ECompileMode.All); 19 if (result != r) throw new Exception("result error"); 20 } 21 22 [Benchmark] 23 public void ZExpressions2() 24 { 25 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 26 var result = Z.Expressions.Eval.Execute<int>(s); 27 if (result != r) throw new Exception("result error"); 28 } 29 30 // 编译缓存 31 [Benchmark] 32 public void AScript3_Cache() 33 { 34 var script = new Script(); 35 var result = script.Eval<int>(s, -1); 36 if (result != r) throw new Exception("result error"); 37 } 38 39 [Benchmark] 40 public void ZExpressions3() 41 { 42 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 43 var result = Z.Expressions.Eval.Execute<int>(s); 44 if (result != r) throw new Exception("result error"); 45 }
测试结果:

性能:AScript > Z.Expressions.Eval
3.2 本地变量
1 private static readonly string s = "var a = 100; var b = 5; var c = 6; a * (b + 5) * (c-2)"; 2 private static readonly int r = 100 * (5 + 5) * (6 - 2); 3 4 // 解释执行 5 [Benchmark] 6 public void AScript1() 7 { 8 var script = new Script(); 9 var result = script.Eval<int>(s); 10 if (result != r) throw new Exception("result error"); 11 } 12 13 // 编译执行 14 [Benchmark] 15 public void AScript2_Compile() 16 { 17 var script = new Script(); 18 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 19 script.Options.RewriteFunctions = false; 20 script.Options.RewriteVariables = false; 21 var result = script.Eval<int>(s, ECompileMode.All); 22 if (result != r) throw new Exception("result error"); 23 } 24 25 [Benchmark] 26 public void ZExpressions2() 27 { 28 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 29 var result = Z.Expressions.Eval.Execute<int>(s); 30 if (result != r) throw new Exception("result error"); 31 } 32 33 // 编译缓存 34 [Benchmark] 35 public void AScript3_Cache() 36 { 37 var script = new Script(); 38 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 39 script.Options.RewriteFunctions = false; 40 script.Options.RewriteVariables = false; 41 var result = script.Eval<int>(s, -1); 42 if (result != r) throw new Exception("result error"); 43 } 44 45 [Benchmark] 46 public void ZExpressions3() 47 { 48 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 49 var result = Z.Expressions.Eval.Execute<int>(s); 50 if (result != r) throw new Exception("result error"); 51 }
测试结果:

性能:AScript > Z.Expressions.Eval
3.3 注入变量
1 private static readonly string s = "a * (b + 5) * (c-2)"; 2 private static readonly int r = 100 * (5 + 5) * (6 - 2); 3 4 // 解释执行 5 [Benchmark] 6 public void AScript1() 7 { 8 var script = new Script(); 9 script.Context.SetVar("a", 100); 10 script.Context.SetVar("b", 5); 11 script.Context.SetVar("c", 6); 12 var result = script.Eval<int>(s); 13 if (result != r) throw new Exception("result error"); 14 } 15 16 // 编译执行 17 [Benchmark] 18 public void AScript2_Compile() 19 { 20 var script = new Script(); 21 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 22 script.Options.RewriteFunctions = false; 23 script.Options.RewriteVariables = false; 24 script.Context.SetVar("a", 100); 25 script.Context.SetVar("b", 5); 26 script.Context.SetVar("c", 6); 27 var result = script.Eval<int>(s, ECompileMode.All); 28 if (result != r) throw new Exception("result error"); 29 } 30 31 [Benchmark] 32 public void ZExpressions2() 33 { 34 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 35 var result = Z.Expressions.Eval.Execute<int>(s, new { a = 100, b = 5, c = 6 }); 36 if (result != r) throw new Exception("result error"); 37 } 38 39 // 编译缓存 40 [Benchmark] 41 public void AScript3_Cache() 42 { 43 var script = new Script(); 44 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 45 script.Options.RewriteFunctions = false; 46 script.Options.RewriteVariables = false; 47 script.Context.SetVar("a", 100); 48 script.Context.SetVar("b", 5); 49 script.Context.SetVar("c", 6); 50 var result = script.Eval<int>(s, -1); 51 if (result != r) throw new Exception("result error"); 52 } 53 54 [Benchmark] 55 public void ZExpressions3() 56 { 57 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 58 var result = Z.Expressions.Eval.Execute<int>(s, new { a = 100, b = 5, c = 6 }); 59 if (result != r) throw new Exception("result error"); 60 }
测试结果:

性能:AScript > Z.Expressions.Eval
3.4 注入方法
1 private static readonly string s = "sum(5, 6)"; 2 private static readonly int r = 11; 3 4 // 解释执行 5 [Benchmark] 6 public void AScript1() 7 { 8 var script = new Script(); 9 //script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); 10 script.Context.AddFunc(typeof(MyMethod)); 11 var result = script.Eval<int>(s); 12 if (result != r) throw new Exception("result error"); 13 } 14 15 // 编译执行 16 [Benchmark] 17 public void AScript2_Compile() 18 { 19 var script = new Script(); 20 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 21 script.Options.RewriteFunctions = false; 22 script.Options.RewriteVariables = false; 23 //script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); 24 script.Context.AddFunc(typeof(MyMethod)); 25 var result = script.Eval<int>(s, ECompileMode.All); 26 if (result != r) throw new Exception("result error"); 27 } 28 29 [Benchmark] 30 public void ZExpressions2() 31 { 32 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 33 Z.Expressions.EvalManager.DefaultContext.RegisterStaticMethod(typeof(MyMethod)); 34 var result = Z.Expressions.Eval.Execute<int>(s); 35 if (result != r) throw new Exception("result error"); 36 } 37 38 // 编译缓存 39 [Benchmark] 40 public void AScript3_Cache() 41 { 42 var script = new Script(); 43 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 44 script.Options.RewriteFunctions = false; 45 script.Options.RewriteVariables = false; 46 //script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); 47 script.Context.AddFunc(typeof(MyMethod)); 48 var result = script.Eval<int>(s, -1); 49 if (result != r) throw new Exception("result error"); 50 } 51 52 [Benchmark] 53 public void ZExpressions3() 54 { 55 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 56 Z.Expressions.EvalManager.DefaultContext.RegisterStaticMethod(typeof(MyMethod)); 57 var result = Z.Expressions.Eval.Execute<int>(s); 58 if (result != r) throw new Exception("result error"); 59 } 60 61 private class MyMethod 62 { 63 public static int sum(int a, int b) => a + b; 64 }
测试结果:

性能:AScript > Z.Expressions.Eval
3.5 本地函数
1 private static readonly string s1 = @" 2 int sum(int a, int b) { 3 return a + b; 4 } 5 sum(5, 6) 6 "; 7 8 // Z.Expressions.Eval定义函数需要加public修饰 9 private static readonly string s2 = @" 10 public int sum(int a, int b) { 11 return a + b; 12 } 13 sum(5, 6) 14 "; 15 16 private static readonly int r = 11;
测试结果:

性能:AScript > Z.Expressions.Eval
3.6 LINQ
1 private static readonly List<int> _list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 2 private static readonly string s = "list.Where(a => a % 2 == 0).ToList().Count"; 3 private static readonly int r = 5;
测试结果:

性能:AScript > Z.Expressions.Eval
3.7 编译
仅编译生成委托,不计算结果。
1 private static readonly string s = @" 2 var list = new List<int>() { 1, 2, 3, 4 }; 3 return list.Where(x => x > 2).ToList();"; 4 5 [Benchmark] 6 public void AScript1() 7 { 8 var script = new Script(); 9 var result = script.Compile<List<int>>(s); 10 } 11 12 [Benchmark] 13 public void ZExpressions1() 14 { 15 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 16 var result = Z.Expressions.Eval.Compile<Func<List<int>>>(s); 17 } 18 19 [Benchmark] 20 public void AScript2_Cache() 21 { 22 var script = new Script(); 23 var result = script.Compile<List<int>>(s, -1); 24 } 25 26 [Benchmark] 27 public void ZExpressions2() 28 { 29 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 30 var result = Z.Expressions.Eval.Compile<Func<List<int>>>(s); 31 }
测试结果:

性能:二者相差不大
3.8 for循环
1 private static readonly string s = @" 2 var n = 0; 3 for (var i = 0; i < 10000; i++) { 4 n += i; 5 } 6 n 7 "; 8 private static readonly int r = 9999 * 5000; 9 10 // 解释执行 11 [Benchmark] 12 public void AScript1() 13 { 14 var script = new Script(); 15 var result = script.Eval<int>(s); 16 if (result != r) throw new Exception("result error"); 17 } 18 19 // 只编译循环语句 20 [Benchmark] 21 public void AScript1_Loop() 22 { 23 var script = new Script(); 24 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 25 script.Options.RewriteFunctions = false; 26 script.Options.RewriteVariables = false; 27 var result = script.Eval<int>(s, ECompileMode.Loop); 28 if (result != r) throw new Exception("result error"); 29 } 30 31 // 编译执行 32 [Benchmark] 33 public void AScript2_Compile() 34 { 35 var script = new Script(); 36 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 37 script.Options.RewriteFunctions = false; 38 script.Options.RewriteVariables = false; 39 var result = script.Eval<int>(s, ECompileMode.All); 40 if (result != r) throw new Exception("result error"); 41 } 42 43 [Benchmark] 44 public void ZExpressions2() 45 { 46 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 47 var result = Z.Expressions.Eval.Execute<int>(s); 48 if (result != r) throw new Exception("result error"); 49 } 50 51 // 编译缓存 52 [Benchmark] 53 public void AScript3_Cache() 54 { 55 var script = new Script(); 56 // Z.Expressions.Eval不回写临时变量到上下文,这里也关闭变量回写功能,保持功能一致 57 script.Options.RewriteFunctions = false; 58 script.Options.RewriteVariables = false; 59 var result = script.Eval<int>(s, -1); 60 if (result != r) throw new Exception("result error"); 61 } 62 63 [Benchmark] 64 public void ZExpressions3() 65 { 66 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 67 var result = Z.Expressions.Eval.Execute<int>(s); 68 if (result != r) throw new Exception("result error"); 69 }
测试结果:

- AScript解释执行大循环语句性能较低,编译循环语句性能有提升;
- 编译执行模式,二者性能相差不大
3.9 LINQ Dynamic
1 private static readonly List<int> _list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 2 private static readonly IQueryable<int> _query = _list.AsQueryable(); 3 private static readonly int r = 5; 4 5 [Benchmark] 6 public void Queryable_AScript() 7 { 8 // 此场景一般是拼接查询条件字符串,AScript不提供编译缓存方案 9 var result = _query.WhereScript(a => "a % 2 == 0").Count(); 10 if (result != r) throw new Exception("result error"); 11 } 12 13 [Benchmark] 14 public void Queryable_ZExpressions() 15 { 16 // 此场景一般是拼接查询条件字符串,不使用缓存 17 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 18 var result = _query.WhereDynamic(a => "a % 2 == 0").Count(); 19 if (result != r) throw new Exception("result error"); 20 } 21 22 [Benchmark] 23 public void Queryable_ZExpressions_Cache() 24 { 25 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 26 var result = _query.WhereDynamic(a => "a % 2 == 0").Count(); 27 if (result != r) throw new Exception("result error"); 28 } 29 30 [Benchmark] 31 public void Enumerable_AScript() 32 { 33 // 此场景一般是拼接查询条件字符串,AScript不提供编译缓存方案 34 var result = _list.WhereScript(a => "a % 2 == 0").Count(); 35 if (result != r) throw new Exception("result error"); 36 } 37 38 [Benchmark] 39 public void Enumerable_ZExpressions() 40 { 41 // 此场景一般是拼接查询条件字符串,不使用缓存 42 Z.Expressions.EvalManager.DefaultContext.UseCache = false; 43 var result = _list.WhereDynamic(a => "a % 2 == 0").Count(); 44 if (result != r) throw new Exception("result error"); 45 } 46 47 [Benchmark] 48 public void Enumerable_ZExpressions_Cache() 49 { 50 Z.Expressions.EvalManager.DefaultContext.UseCache = true; 51 var result = _list.WhereDynamic(a => "a % 2 == 0").Count(); 52 if (result != r) throw new Exception("result error"); 53 }
测试结果:

- 性能:AScript > Z.Expressions.Eval
- IEnumerable扩展方法,Z.Expressions.Eval开启缓存时性能高,因为少了编译操作
四 总结
| 功能 | AScript | Z.Expressions.Eval |
|---|---|---|
| .NET框架 | net45 netstandard2.0 netstandard2.1 net6.0 net8.0 |
net40 net45 netstandard2.0 netstandard2.1 net6.0 net8.0 net10.0 |
| 开源 | 免费MIT开源 | 收费 |
| 性能 | 高 | 高 |
| 内存占用 | 低 | 中 |
| 执行模式 | 解释执行+编译执行+编译缓存 | 编译执行+编译缓存 |
| 缓存版本控制 | 支持 | 不支持 |
| Lambda | 支持 | 支持 |
| LINQ查询语法 | 支持 | 不支持 |
| 递归函数 | 支持 | 不支持 |
| 自定义语法 | 支持 | 不支持 |
- 执行小体量脚本时,AScript的解释执行模式性能较高,适合执行碎片化临时脚本的场景;
- 编译执行模式,两者性能相差不大,适合执行临时、大体量、大循环脚本的场景;
- 编译缓存模式,AScript性能比Z.Expressions.Eval高,适合执行高频、脚本固定或变化少的场景;
- AScript扩展能力强,支持多种脚本语言。
综上,AScript脚本引擎优势还是比较明显的。

浙公网安备 33010602011771号