1

I'm trying to use Roslyn to execute C# code that is defined by the user at runtime, similar to this example:

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

My problem is that the variable names used in the script are unknown at compile time. In other words, I don't know what member-names I should use for my globals class and how many members (script-parameters) there will be.

I tried to use ExpandoObject to solve the problem but couldn't get it to work. Is ExpandoObject supposed to work in this context? Are there other ways to solve the problem?

Update

For my use case, the best solution is probably to use 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));
}
Krid
  • 95
  • 1
  • 11

1 Answers1

0

If you can retrieve at runtime all member names, their count and their values that were passed from input you can generate execution code at runtime and evaluate it. As a simple example of execution code you can generate variable declarations for all input values and then sum all of them:

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

Be careful, this example assumes that the all value types has sum_operation and them are known by default script options, else you will receive compile error when try to execute the code.

Upd.

If your cases are performance critically you may create a script that will sum all input arguments and then run this script repeatedly when you need it.

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;

This approach ignore in the script code the input variable names from an user, and seems that it doesn't matter in your cases.

George Alexandria
  • 2,841
  • 2
  • 16
  • 24
  • My use case is quite performance critical, the script-code will be executed 1000 times per second or more. Therefore I was hoping that I could pre-compile the code. Your solution requires a re-compilation every time a value changes. But I agree, otherwise this would work. – Krid Nov 25 '19 at 10:53
  • @Krid, the script code will compile and execute at every time when a values will changed, that's true. You can not avoid execution at every time, because you pass a different values that produce a different result, but yes, you can "precompile" (it doesn't quite correct to call it as compilation of CsharpScript) script code at once and execute every time when values will changed. However, this approach will ignore input value names still script is executing. So are value names matter when script is executing? If they don't matter, let me know and I will post fixed solution with precompilation. – George Alexandria Nov 25 '19 at 17:30
  • the use case is as follows: The user defines a boolean expression like (A+B > C). The count and the names of the variables are up to the user. Each variable represents a certain value (the connection VariableName<->Value is made elsewhere). All values are updated at a configurable rate (usually 1000Hz) and the expression has to be evaluated on each update. Since the expression is created only once but is executed many, times, I was hoping that I could use CSharpScript.Create/Compile once after the user typed in the expression and then repeatedly run the "compiled" script. – Krid Nov 26 '19 at 08:16
  • @Krid, the answer was updated. Let me know, if I missed something again – George Alexandria Nov 26 '19 at 18:07
  • Thanks for your help but that solution would require that the user has to type "Args[0] + Args[1] > Args[2]" instead of "A + B > C". Alternatively I would have to parse the expression and replace "A" by "Args[0]" etc. I have found a much more easy solution using System.Linq.Dynamic, see updated question. – Krid Nov 27 '19 at 14:29