首页 > 解决方案 > CSharpScript:动态脚本参数名称

问题描述

我正在尝试使用 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));

从这里复制的示例

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

我尝试使用 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#roslyncsharpscript

解决方案


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

// 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;

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


推荐阅读