【问题标题】:CSharpScript: Dynamic script parameter namesCSharpScript:动态脚本参数名称
【发布时间】:2019-11-22 09:02:07
【问题描述】:

我正在尝试使用 Roslyn 执行用户在运行时定义的 C# 代码,类似于此示例:

public class Globals
{
    public int X;
    public int Y;
}
var globals = new Globals { X = 1, Y = 2 };
Console.WriteLine(await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals));

Example copied from here

我的问题是脚本中使用的变量名在编译时是未知的。换句话说,我不知道我的全局类应该使用什么成员名称以及会有多少成员(脚本参数)。

我尝试使用 ExpandoObject 来解决问题,但无法正常工作。 ExpandoObject 应该在这种情况下工作吗?有没有其他方法可以解决这个问题?

更新

对于我的用例,最好的解决方案可能是使用System.Linq.Dynamic

//Expression typed in by the user at runtime
string Exp = @"A + B > C";

//The number of variables and their names are given elsewhere,
//so the parameter-array doesn't need to be hardcoded like in this example.
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] 
{
    Expression.Parameter(typeof(double), "A"),
    Expression.Parameter(typeof(double), "B"),
    Expression.Parameter(typeof(double), "C")
}, 
null, Exp);

var Lambda = e.Compile();

//Fake some updates...
foreach (var i in Enumerable.Range(0,10))
{
    Console.WriteLine(Lambda.DynamicInvoke(i, 3, 10));
}

【问题讨论】:

    标签: c# roslyn csharpscript


    【解决方案1】:

    如果您可以在运行时检索从输入传递的所有成员名称、计数和值,则可以在运行时生成执行代码并对其进行评估。作为执行代码的一个简单示例,您可以为所有输入值生成变量声明,然后将它们全部相加:

    // here you should put retrieved member names and their values. Just  for example, currently here exist a couple of args
    var variablesAndValues = new Dictionary<string, object> { ["arg_1"] = 5, ["arg_2"] = 6, ["arg_3"] = 7 };
    
    // and you should create an execution code looks like this:
    // var arg_1 = value_1;
    // ..
    // var arg_n = value_n;
    // arg_1 + .. + arg_n
    StringBuilder builder = new StringBuilder();
    foreach (var item in variablesAndValues)
    {
        builder.Append("var ").Append(item.Key).Append(" = ").Append(item.Value).AppendLine(";");
    }
    
    var variablesCount = variablesAndValues.Count;
    foreach (var item in variablesAndValues.Keys)
    {
        builder.Append(item);
        if (--variablesCount > 0)
        {
            builder.Append(" + ");
        }
    }
    
    var scriptState = CSharpScript.RunAsync(builder.ToString()).Result;
    var result = scriptState.ReturnValue;
    

    请注意,此示例假定所有值类型都有sum_operation,并且它们在默认脚本选项中是已知的,否则在尝试执行代码时会收到编译错误。

    更新。

    如果您的案例对性能要求很高,您可以创建一个脚本来汇总所有输入参数,然后在需要时重复运行此脚本。

    public class Globals
    {
        public int[] Args;
    }
    ...
    
    // create script that sum all input arguments
    var script = CSharpScript.Create(@"var result = 0;
    foreach (var item in Args)
    {
        result += item;
    }
    return result; ", globalsType: typeof(Globals));
    
    // run it at twice on the user values that were received before
    // also you can reuse an array, but anyway
    var res1 = script.RunAsync(new Globals { Args = new[] { 5, 6, 7 } }).Result.ReturnValue;
    var res2 = script.RunAsync(new Globals { Args = new[] { 7, 8, 9, 10} }).Result.ReturnValue;
    

    这种方法在脚本代码中忽略来自用户的输入变量名称,并且在您的情况下似乎无关紧要。

    【讨论】:

    • 我的用例对性能非常关键,脚本代码每秒会执行 1000 次或更多。因此,我希望我可以预编译代码。每次值更改时,您的解决方案都需要重新编译。但我同意,否则这会起作用。
    • @Krid,脚本代码将在每次更改值时编译并执行,这是真的。您不能每次都避免执行,因为您传递了不同的值会产生不同的结果,但是是的,您可以一次“预编译”(将其称为 CsharpScript 的编译不太正确)脚本代码并执行每次值都会改变。但是,此方法将忽略仍在执行脚本的输入值名称。那么当脚本执行时值名称很重要吗?如果它们无关紧要,请告诉我,我将发布带有预编译的固定解决方案。
    • 用例如下: 用户定义一个布尔表达式,如 (A+B > C)。变量的计数和名称由用户决定。每个变量代表一个特定值(连接 VariableNameValue 在别处建立)。所有值都以可配置的速率(通常为 1000Hz)更新,并且必须在每次更新时评估表达式。由于表达式只创建一次但执行了很多次,我希望我可以在用户输入表达式后使用 CSharpScript.Create/Compile 一次,然后重复运行“编译”脚本。
    • @Krid,答案已更新。如果我又错过了什么,请告诉我
    • 感谢您的帮助,但该解决方案要求用户必须键入“Args[0] + Args[1] > Args[2]”而不是“A + B > C”。或者,我必须解析表达式并将“A”替换为“Args [0]”等。我发现使用 System.Linq.Dynamic 的解决方案更简单,请参阅更新后的问题。
    猜你喜欢
    • 1970-01-01
    • 2019-08-22
    • 2011-11-29
    • 2019-03-24
    • 2017-03-17
    • 1970-01-01
    • 2015-02-16
    • 2023-03-31
    • 1970-01-01
    相关资源
    最近更新 更多