0

I am trying to use the new Roslyn scripting modules. This is an example of how to use it. Notice that Globals appears to need to be a class.

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

I have a generic function that takes a type T, with the length of the array indeterminate (but relatively small length in most cases):

void Func<T>()
{
   T[] values;
}

How do I convert the T[] to an anonymous type?

So if I have T if of type decimal and in this case values is of length 3,

values[0] = 124.3, values[1] = 132.4, values[2] = 23

I would like to have an anonymous type created that looks something like this:

var v = new { v1 = 124.3, v2 = 232.4, v3 = 23 };

Is this possible? That is, to create an anonymous type from an array that you don't know the length of at compile time?

NOTE: This is why I need an anonymous type and not a tuple, or a List etc. And, since I don't know how big the array is, I can't hard wire a class

Edit 1

I was somewhat shocked when I tried the solution given below that this even compiles:

dynamic v = new ExpandoObject();
var dictionary = (IDictionary<string, object>)v;            

dictionary.Add("X", 1.5);
dictionary.Add("Y", 2.7);

//var globals = new Globals { X = 1.5, Y = 2.7 };
var retval = CSharpScript.EvaluateAsync<double>("System.Math.Sqrt(System.Math.Log(X + Y))", 
                      globals: dictionary).GetAwaiter();

//retval = (decimal)Convert.ChangeType(retval, typeof(decimal));

Console.WriteLine(retval.GetResult());

Sadly, I get a runtime error:

Microsoft.CodeAnalysis.Scripting.CompilationErrorException
  HResult=0x80131500
  Message=(1,34): error CS0103: The name 'X' does not exist in the current context
  Source=Microsoft.CodeAnalysis.Scripting
  StackTrace:
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, DiagnosticFormatter formatter)
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, Boolean emitDebugInformation, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.GetExecutor(CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Trady.Form1.InitializeScriptEngineAsync() in C:\Users\idf\Form1.cs:line 79
   at Form1..ctor() in C:\Users\idf\Form1.cs:line 56
   at Trady.Program.Main() in C:\Users\idf\Program.cs:line 19
Ivan
  • 7,448
  • 14
  • 69
  • 134
  • The index is irrelevant to me. Just the solution. – Ivan Apr 18 '19 at 20:17
  • Have you considered `List`? – Amir Molaei Apr 18 '19 at 20:18
  • See edited post for why I need an anonymous type. – Ivan Apr 18 '19 at 20:21
  • You could use an ExpandoObject and return dynamic. You couldn't use an anonymous type because the shape of the class is not known at compile type, it's determined by the size of the array at runtime. Thus, the object is dynamic – Steve Apr 18 '19 at 20:22
  • Can you give an example? – Ivan Apr 18 '19 at 20:23
  • 1
    Why can't the array itself be a named property on a "globals" wrapper, e.g.: `CSharpScript.EvaluateAsync("v[0]+v[1]", globals: new { v = new [] { 1, 2 }}).GetAwaiter()`? – canon Apr 18 '19 at 20:58
  • 1
    Would https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples#chain that work for you (may be ok if you just have simple values)… Otherwise probably duplicate of https://stackoverflow.com/questions/3862226/how-to-dynamically-create-a-class-in-c – Alexei Levenkov Apr 18 '19 at 21:09
  • @canon, because you would get a runtime error when the string expression tries to run since there is no "X" – Ivan Apr 18 '19 at 23:27
  • @Alexie, I had not seen that way of building the script and I can make that work! I fear performance is painfully slow. The "globals" version allows you to build the script once, and then just pass/bind new parameters from then on. Waaay faster. – Ivan Apr 18 '19 at 23:39
  • @Ivan there's no X in the expression I suggested. – canon Apr 19 '19 at 02:36
  • With the Edit 1, pass the dynamic object `globals: v` rather than the underlying dictionary `globals: dictionary` – Ivan Stoev Apr 19 '19 at 08:21
  • @ivan Stoev, same error. – Ivan Apr 19 '19 at 18:15

2 Answers2

1

The problem is that anonymous types are autogenerated types and they're fixed at compiletime. So it's a statically typed dynamic type.

ExpandoObject is the object that you use with the dynamic keyword in order to add properties and methods dynamically.

Here is an example for your function:

void Func<T>()
{
    T[] values;
    dynamic v = new ExpandoObject();
    var dictionary = (IDictionary<string, object>)v;
    var i = 0;
    foreach (var value in values)
    {
        i++;
        dictionary.Add($"v{i}", value);
    }
}

The ExpandoObject implements the IDictionary Interface and as such can be cast to it, in order to add properties dynamically.

Raul
  • 2,745
  • 1
  • 23
  • 39
0

You can use a dictionary of values by converting the dictionary into a string of variable declaration code that executes before the equation code. The following sample handles numeric data types and strings. The GetDeclaration method can be customized to support other data types such as DateTime or custom classes.

private static void Main()
{
    // Declare a dictionary with desired variables
    var values = new Dictionary<string, object>
    {
        { "X", (int) 1 },
        { "Y", (decimal) 2 },
        { "flag", true },
        { "option", "test" }
    };

    // Convert variables into code declarations
    string declareValues = string.Join(" ", values.Select(v => GetDeclaration(v.Key, v.Value)));
    // declareValues = System.Int32 X = 1; System.Decimal Y = 2; System.Boolean flag = true; System.String option = "test";

    string code = "X + Y";

    // Run the variable declaration code before the equation code
    var evalResult = CSharpScript.EvaluateAsync(declareValues + code).Result;
    // evalResult = (decimal) 3

    Console.WriteLine($"Variables: {declareValues}");
    Console.WriteLine($"{code} = {evalResult}");
}

private static string GetDeclaration(string name, object value)
{
    var type = value.GetType();
    string valueCode;

    if (value is string)
    {
        valueCode = $"@\"{((string)value).Replace("\"", "\"\"")}\"";
    }
    else if (value is bool)
    {
        valueCode = value.ToString().ToLower();
    }
    else
    {
        valueCode = value.ToString();
    }

    return $"{type} {name} = {valueCode};";
}
mrBlack
  • 1
  • 2