欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-09 20:50:31
基于 Roslyn 实现一个简单的条件解析引擎 Intro 最近在做一个勋章的服务,我们想定义一些勋章的获取条件,满足条件之后就给用户颁发一个勋章,定义条件的时候会定义需要哪些参数,参数的类型,获取勋章的时候会提供锁需要的参数,有一些内置的参数,内置的参数解析器(ParamResolver)。 最后 ......

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

intro

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

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

condition eval demo

条件解析示例:

[fact]
public async task evaltest()
{
    var condition = "x+y > 10";
    var variables = jsonconvert.serializeobject(new[]
    {
        new
        {
            name = "x",
            type = "int"
        },
        new
        {
            name = "y",
            type = "int"
        },
    });

    var params1 = new dictionary<string, object>()
    {
        { "x", 2 },
        { "y", 3 }
    };
    assert.false(await scriptengine.evalasync(condition, variables, params1));

    var params1_1 = jsonconvert.serializeobject(params1);
    assert.false(await scriptengine.evalasync(condition, variables, params1_1));

    var params2 = new
    {
        x = 6,
        y = 5
    };
    assert.true(await scriptengine.evalasync(condition, variables, params2));
}

[fact]
public async task evalstringtest()
{
    var condition = "x > y.length";
    var variables = jsonconvert.serializeobject(new[]
    {
        new
        {
            name = "x",
            type = "int"
        },
        new
        {
            name = "y",
            type = "string"
        },
    });

    var params1 = new
    {
        x = 1,
        y = "3"
    };
    assert.false(await scriptengine.evalasync(condition, variables, params1));

    var params2 = new
    {
        x = 6,
        y = "5211"
    };
    assert.true(await scriptengine.evalasync(condition, variables, params2));
}

[fact]
public async task evallinqtest()
{
    var condition = "list.any(x=>x>10)";
    var variables = jsonconvert.serializeobject(new[]
    {
        new
        {
            name = "list",
            type = "list<int>"
        }
    });

    var params1 = new
    {
        list = new list<int>()
        {
            1,2,3,4,5
        }
    };
    assert.false(await scriptengine.evalasync(condition, variables, params1));

    var params2 = new
    {
        list = new list<int>()
        {
            1,2,3,4,5,10,12
        }
    };
    assert.true(await scriptengine.evalasync(condition, variables, params2));
}

实现原理

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

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 来加载程序集

#if netcoreapp
    var assembly = assemblyloadcontext.default.loadfromassemblypath(dllpath);
#else
    var assembly = assembly.loadfile(dllpath);
#endif

程序集要保存到文件

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

roslyn 动态生成的程序集管理

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

more

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

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

reference