大数跨境
0
0

基于 Roslyn 实现一个简单的条件解析引擎

基于 Roslyn 实现一个简单的条件解析引擎 amazingdotnet
2020-03-19
0
导读:基于 Roslyn 实现一个的条件解析引擎

基于 Roslyn 实现一个简单的条件解析引擎

Intro

最近在做一个勋章的服务,我们想定义一些勋章的获取条件,满足条件之后就给用户颁发一个勋章,定义条件的时候会定义需要哪些参数,参数的类型,获取勋章的时候会提供所需要的参数,有一些内置的参数,内置的参数解析器(ParamResolver)。

最后基于 Roslyn 的 Script + 动态编译功能实现了一个简单的条件解析引擎。

Condition Eval Demo

条件解析示例:

 
  1. [Fact]

  2. public async Task EvalTest()

  3. {

  4. var condition = "x+y > 10";

  5. var variables = JsonConvert.SerializeObject(new[]

  6. {

  7. new

  8. {

  9. Name = "x",

  10. Type = "int"

  11. },

  12. new

  13. {

  14. Name = "y",

  15. Type = "int"

  16. },

  17. });


  18. var params1 = new Dictionary<string, object>()

  19. {

  20. { "x", 2 },

  21. { "y", 3 }

  22. };

  23. Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));


  24. var params1_1 = JsonConvert.SerializeObject(params1);

  25. Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1_1));


  26. var params2 = new

  27. {

  28. x = 6,

  29. y = 5

  30. };

  31. Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));

  32. }


  33. [Fact]

  34. public async Task EvalStringTest()

  35. {

  36. var condition = "x > y.Length";

  37. var variables = JsonConvert.SerializeObject(new[]

  38. {

  39. new

  40. {

  41. Name = "x",

  42. Type = "int"

  43. },

  44. new

  45. {

  46. Name = "y",

  47. Type = "string"

  48. },

  49. });


  50. var params1 = new

  51. {

  52. x = 1,

  53. y = "3"

  54. };

  55. Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));


  56. var params2 = new

  57. {

  58. x = 6,

  59. y = "5211"

  60. };

  61. Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));

  62. }


  63. [Fact]

  64. public async Task EvalLinqTest()

  65. {

  66. var condition = "list.Any(x=>x>10)";

  67. var variables = JsonConvert.SerializeObject(new[]

  68. {

  69. new

  70. {

  71. Name = "list",

  72. Type = "List<int>"

  73. }

  74. });


  75. var params1 = new

  76. {

  77. list = new List<int>()

  78. {

  79. 1,2,3,4,5

  80. }

  81. };

  82. Assert.False(await ScriptEngine.EvalAsync(condition, variables, params1));


  83. var params2 = new

  84. {

  85. list = new List<int>()

  86. {

  87. 1,2,3,4,5,10,12

  88. }

  89. };

  90. Assert.True(await ScriptEngine.EvalAsync(condition, variables, params2));

  91. }

实现原理

实现的方式是基于 Roslyn 实现的,核心实现是基于 Roslyn 的 Script 实现的,但是 Roslyn Script 的执行有一些限制,不支持匿名类对象的解析,因此还基于 Roslyn 运行时根据变量信息来动态生成一个类型用于执行脚本解析

 
  1. var result = await CSharpScript.EvaluateAsync<bool>("1 > 2");

运行时动态生成代码在之前的 DbTool 项目中介绍过,介绍文章 基于 Roslyn 实现动态编译

详细实现细节可以参考代码 https://github.com/WeihanLi/SamplesInPractice/tree/master/ScriptEngine

Memo

程序集加载在 framework 和 core 环境下的差异

实现的时候我们的项目有 dotnetcore 的,还有 netframework 的,这两者加载 dll 的时候略有不同,实现的时候用了一个条件编译,在 dotnet core 环境下和 dotnet framework 分开处理,在 dotnetcore 中使用 AssemblyLoadContext 来加载程序集

 
  1. #if NETCOREAPP

  2. var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dllPath);

  3. #else

  4. var assembly = Assembly.LoadFile(dllPath);

  5. #endif

程序集要保存到文件

原本打算动态生成的程序集保存的一个 Stream 不保存文件,但是实际测试下来必须要保存到文件才可以,所以在项目根目录下创建了一个临时目录 temp 用来保存动态生成的程序集

Roslyn 动态生成的程序集管理

目前还是比较简单的放在一个 temp 目录下了,总觉得每一个类型生成一个程序集有些浪费,但是好像也没办法修改已有程序集,还没找到比较好的解决方案,如果有好的处理方式,欢迎一起交流

More

Natasha 是一个基于 Roslyn 来实现动态编译,能够让你更方便进行动态操作,有动态编译相关需求的可以关注一下这个项目,后面也想用 Natasha 来优化前面提到的问题

基于roslyn的动态编译库,为您提供高效率、高性能、可追踪的动态构建方案,兼容stanadard2.0, 只需原生C#语法不用Emit。让您的动态方法更加容易编写、跟踪、维护

Reference

  • https://github.com/WeihanLi/SamplesInPractice/tree/master/ScriptEngine

  • https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples

  • https://github.com/dotnetcore/Natasha


【声明】内容源于网络
0
0
amazingdotnet
dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
内容 539
粉丝 0
amazingdotnet dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
总阅读248
粉丝0
内容539