基於 Roslyn 實現一個簡單的條件解析引擎


基於 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM