c# - 如何将 "using static"指令用于动态生成的代码?

标签 c# roslyn-code-analysis

我想让用户尽可能自然地输入xy的数学表达式。例如,我更喜欢只使用 Sin(x),而不是键入 Complex.Sin(x)

例如,当 Sin(x) 由用户定义时,以下代码将失败。

using Microsoft.CodeAnalysis.CSharp.Scripting;
using System;
using System.Numerics;
using static System.Console;
using static System.Numerics.Complex;


namespace MathEvaluator
{
    public class Globals
    {
        public Complex x;
        public Complex y;
    }

    class Program
    {

        async static void JobAsync(Microsoft.CodeAnalysis.Scripting.Script<Complex> script)
        {
            Complex x = new Complex(1, 0);
            Complex y = new Complex(0, 1);
            try
            {
                var result = await script.RunAsync(new Globals { x = x, y = y });
                WriteLine($"{x} * {y} = {result.ReturnValue}\n");
            }
            catch (Exception e)
            {
                WriteLine(e.Message);
            }
        }

        static void Main(string[] args)
        {

            Console.Write("Define your expression in x and y: ");
            string expression = Console.ReadLine(); //user input

            var script = CSharpScript.Create<Complex>(expression, globalsType: typeof(Globals));
            script.Compile();

            JobAsync(script);

        }
    }
}

问题

How to use using static directive for dynamically generated code?

最佳答案

您可以向 Create 函数提供脚本选项,以定义应为您的脚本设置的引用和导入:

var scriptOptions = ScriptOptions.Default
    .WithReferences("System.Numerics")
    .WithImports("System.Numerics.Complex");

var script = CSharpScript.Create<Complex>(expression, options: scriptOptions, globalsType: typeof(Globals));

这样,您可以在输入中使用 Sin(x):

Define your expression in x and y: Sin(x)
(1, 0) * (0, 1) = (0,841470984807897, 0)

但是,在处理用户输入时,您应该考虑编写自己的解析器。一方面,这允许您为函数定义自己的“别名”(例如小写的 sin)或什至更宽松的语法;另一方面,它也增加了更多的安全性,因为现在,没有什么能阻止我这样做:

Define your expression in x and y: System.Console.WriteLine("I hacked this calculator!")
I hacked this calculator!
(1, 0) * (0, 1) = (0, 0)

我使用 Roslyn 的语法树解析创建了一个快速(但很脏)的解析器。显然这是相当有限的(例如,因为它要求子表达式的所有返回值都是 Complex),但这可以让您了解它是如何工作的:

void Main()
{
    string input = "y + 3 * Sin(x)";
    var options = CSharpParseOptions.Default.WithKind(Microsoft.CodeAnalysis.SourceCodeKind.Script);
    var expression = CSharpSyntaxTree.ParseText(input, options).GetRoot().DescendantNodes().OfType<ExpressionStatementSyntax>().FirstOrDefault()?.Expression;

    Console.WriteLine(EvaluateExpression(expression));
}

Complex EvaluateExpression(ExpressionSyntax expr)
{
    if (expr is BinaryExpressionSyntax)
    {
        var binExpr = (BinaryExpressionSyntax)expr;
        var left = EvaluateExpression(binExpr.Left);
        var right = EvaluateExpression(binExpr.Right);

        switch (binExpr.OperatorToken.ValueText)
        {
            case "+":
                return left + right;
            case "-":
                return left - right;
            case "*":
                return left * right;
            case "/":
                return left / right;
            default:
                throw new NotSupportedException(binExpr.OperatorToken.ValueText);
        }
    }
    else if (expr is IdentifierNameSyntax)
    {
        return GetValue(((IdentifierNameSyntax)expr).Identifier.ValueText);
    }
    else if (expr is LiteralExpressionSyntax)
    {
        var value = ((LiteralExpressionSyntax)expr).Token.Value;
        return float.Parse(value.ToString());
    }
    else if (expr is InvocationExpressionSyntax)
    {
        var invocExpr = (InvocationExpressionSyntax)expr;
        var args = invocExpr.ArgumentList.Arguments.Select(arg => EvaluateExpression(arg.Expression)).ToArray();
        return Call(((IdentifierNameSyntax)invocExpr.Expression).Identifier.ValueText, args);
    }
    else
        throw new NotSupportedException(expr.GetType().Name);
}

Complex Call(string identifier, Complex[] args)
{
    switch (identifier.ToLower())
    {
        case "sin":
            return Complex.Sin(args[0]);
        default:
            throw new NotImplementedException(identifier);
    }
}

Complex GetValue(string identifier)
{
    switch (identifier)
    {
        case "x":
            return new Complex(1, 0);
        case "y":
            return new Complex(0, 1);
        default:
            throw new ArgumentException("Identifier not found", nameof(identifier));
    }
}

关于c# - 如何将 "using static"指令用于动态生成的代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38306379/

相关文章:

c# - SQL Server CE 没有从另一个进程中获取更新?

c# - c# 是什么语言生成?

roslyn - 如何跨 Workspace.TryApplyChanges() 跟踪 SyntaxNodes

c# - Roslyn - CSharpCompilation 到 CSharpCompilation 引用

c# - .Net Framework 项目上的 Visual Studio 代码分析

c# - Roslyn:从代码分析器访问部分类的 XAML

c# - 如何解析 C# 类的方法体内的所有公共(public)方法名称和方法调用?

c# - 制作带有忙碌图标的鼠标 (C#)

c# - c# windows服务的配置文件

c# - 如何在C#中制作一个简单的动态代理