日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

用Roslyn玩转代码之一: 解析与执行字符串表达式

發布時間:2024/1/11 windows 30 coder
生活随笔 收集整理的這篇文章主要介紹了 用Roslyn玩转代码之一: 解析与执行字符串表达式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??最近框架中的可視化界面設計需要使用到表達式引擎(解析代碼字符串并動態執行),之前舊框架的實現是將表達式字符串解析為語法樹后解釋執行該表達式,本文介紹如何使用Roslyn解析表達式字符串,并直接轉換為Linq的表達式后編譯執行。

一、語法(Syntax)與語義(Semantic)

??C#的代碼通過Roslyn解析為相應的語法樹,并且利用語義分析可以獲取語法節點所對應的符號及類型信息,這樣利用這些信息可以正確的轉換為Linq的表達式。這里作者就不展開了,可以參考Roslyn文檔。

  • 語法分析
    https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis
  • 語義分析
    https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/semantic-analysis
  • 語法轉換
    https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-transformation

二、實現表達式解析器(ExpressionParser)

1. 解析字符串方法

??下面開始創建一個類庫工程,引用包Microsoft.CodeAnalysis.CSharp.Features,然后參照以下代碼創建ExpressionParser類, 靜態ParseCode()方法是解析字符串表達式的入口:

using System.Linq.Expressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ExpEngine;

public sealed class ExpressionParser : CSharpSyntaxVisitor<Expression>
{
    private ExpressionParser(SemanticModel semanticModel)
    {
        _semanticModel = semanticModel;
    }

    private readonly SemanticModel _semanticModel;

    /// <summary>
    /// 解析表達式字符串轉換為Linq的表達式
    /// </summary>
    public static Expression ParseCode(string code)
    {
        var parseOptions = new CSharpParseOptions().WithLanguageVersion(LanguageVersion.CSharp11);
        var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            .WithNullableContextOptions(NullableContextOptions.Enable);

        var tree = CSharpSyntaxTree.ParseText(code, parseOptions);
        var root = tree.GetCompilationUnitRoot();
        var compilation = CSharpCompilation.Create("Expression", options: compilationOptions)
            .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
            .AddSyntaxTrees(tree);
        var semanticModel = compilation.GetSemanticModel(tree);
        //檢查是否存在語義錯誤
        var diagnostics = semanticModel.GetDiagnostics();
        var errors = diagnostics.Count(d => d.Severity == DiagnosticSeverity.Error);
        if (errors > 0)
            throw new Exception("表達式存在語義錯誤");

        var methodDecl = root.DescendantNodes().OfType<MethodDeclarationSyntax>().First();
        if (methodDecl.Body != null && methodDecl.Body.Statements.Count > 1)
            throw new NotImplementedException("Parse block body");

        if (methodDecl.ExpressionBody != null)
            throw new NotImplementedException("Parse expression body");

        var firstStatement = methodDecl.Body!.Statements.FirstOrDefault();
        if (firstStatement is not ReturnStatementSyntax returnNode)
            throw new Exception("表達式方法不是單行返回語句");

        var parser = new ExpressionParser(semanticModel);
        return parser.Visit(returnNode.Expression)!;
    }
}

2. 解析運行時類型的方法

??因為轉換過程中需要將Roslyn解析出來的類型信息轉換為對應的C#運行時的類型,所以需要實現類型轉換的方法:

    private readonly Dictionary<string, Type> _knownTypes = new()
    {
        { "bool", typeof(bool) },
        { "byte", typeof(byte) },
        { "sbyte", typeof(sbyte) },
        { "short", typeof(short) },
        { "ushort", typeof(ushort) },
        { "int", typeof(int) },
        { "uint", typeof(uint) },
        { "long", typeof(long) },
        { "ulong", typeof(ulong) },
        { "float", typeof(float) },
        { "double", typeof(double) },
        { "char", typeof(char) },
        { "string", typeof(string) },
        { "object", typeof(object) },
    };

    /// <summary>
    /// 根據類型字符串獲取運行時類型
    /// </summary>
    private Type ResolveType(string typeName)
    {
        if (_knownTypes.TryGetValue(typeName, out var sysType))
            return sysType;

        //通過反射獲取類型
        var type = Type.GetType(typeName);
        if (type == null)
            throw new Exception($"Can't find type: {typeName} ");

        return type;
    }

3. 解析各類語法節點轉換為對應的Linq表達式

??這里舉一個簡單的LiteralExpression轉換的例子,其他請參考源碼。需要注意的是Linq的表達式嚴格匹配類型簽名,比如方法調用object.Equals(object a, object b), 如果參數a是int類型,需要使用Expression.Convert(int, typeof(object))轉換為相應的類型。

    private Type? GetConvertedType(SyntaxNode node)
    {
        var typeInfo = _semanticModel.GetTypeInfo(node);
        Type? convertedType = null;
        if (!SymbolEqualityComparer.Default.Equals(typeInfo.Type, typeInfo.ConvertedType))
            convertedType = ResolveType((INamedTypeSymbol)typeInfo.ConvertedType!);

        return convertedType;
    }

    public override Expression? VisitLiteralExpression(LiteralExpressionSyntax node)
    {
        var convertedType = GetConvertedType(node);
        var res = Expression.Constant(node.Token.Value);
        return convertedType == null ? res : Expression.Convert(res, convertedType);
    }

三、測試解析與執行表達式

??現在可以創建一個單元測試項目驗證一下解析字符串表達式并執行了,當然實際應用過程中應緩存解析并編譯的表達式委托:

namespace UnitTests;

using static ExpEngine.ExpressionParser;

public class Tests
{
    [Test]
    public void StaticPropertyTest() => Assert.True(Run<object>("DateTime.Today") is DateTime);

    [Test]
    public void InstancePropertyTest() => Run<int>("DateTime.Today.Year");

    [Test]
    public void MethodCallTest1() => Run<DateTime>("DateTime.Today.AddDays(1 + 1)");

    [Test]
    public void MethodCallTest2() => Run<DateTime>("DateTime.Today.AddDays(DateTime.Today.Year)");

    [Test]
    public void MethodCallTest3() => Run<DateTime>("DateTime.Today.AddDays(int.Parse(\"1\"))");

    [Test]
    public void MethodCallTest4() => Assert.True(Run<bool>("Equals(new DateTime(1977,3,1), new DateTime(1977,3,1))"));

    [Test]
    public void PrefixUnaryTest() => Run<DateTime>("DateTime.Today.AddDays(-1)");

    [Test]
    public void NewTest() => Assert.True(Run<DateTime>("new DateTime(1977,3,16)") == new DateTime(1977, 3, 16));

    [Test]
    public void BinaryTest1() => Assert.True(Run<float>("3 + 2.6f") == 3 + 2.6f);

    [Test]
    public void BinaryTest2() => Assert.True(Run<bool>("3 >= 2.6f"));
}

四、 一些限制與TODO

??Linq的表達式本身存在一些限制,請參考文檔:
https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/expression-trees/

??另上述代碼僅示例,比如表達式輸入參數等未實現,小伙伴們可以繼續自行完善。

總結

以上是生活随笔為你收集整理的用Roslyn玩转代码之一: 解析与执行字符串表达式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。