一 简介
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脚本引擎优势还是比较明显的。