Build anonymous type from an array of <T>

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
c#
anonymous-types
c#-7.0
asked on Stack Overflow Apr 18, 2019 by Ivan • edited Apr 18, 2019 by Ivan

2 Answers

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.

answered on Stack Overflow Apr 18, 2019 by Raul Sebastian • edited Apr 18, 2019 by Raul Sebastian
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};";
}
answered on Stack Overflow Jul 9, 2020 by mrBlack • edited Jul 9, 2020 by mrBlack

User contributions licensed under CC BY-SA 3.0