roslyn: Cannot use ExpandoObject as 'globals' parameter for Script.Run()

How to reproduce:

var script = CSharpScript.Create("a + b");

dynamic expando = new ExpandoObject();
var dictionary = (IDictionary<String, Object>) expando;
dictionary.Add("a", 3);
dictionary.Add("b", 4);
script.Run(expando);

Gives error message: The name 'b' does not exist in the current context

Is there another, recommended way of giving a dynamically built object as the globals parameter to the script?

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Reactions: 16
  • Comments: 29 (8 by maintainers)

Most upvoted comments

Also looking for a status update? I’ve implemented the workaround but would prefer not having to access ‘globals’ from another member.

Issue opened on 30 May 2015. We are on Dec 2021 and still no fix. What a shame.

Why Roslyn is not supporting ExpandoObject or Anonymous. Is there any reason?

Still waiting for the fix in the 2022.

Just tried to use ExpandoObject today and it appears this is still not supported. However as I still need to accept on-demand globals just before the script is executed, I am currently trying the following workaround:

  • create a script string that declares the variables needed with default values. The assembly do need to be imported which can be found through reflection. For example, if the variable was an integer named test, the generated script would be int test = default(int);
  • run this generated script
  • use the returned state and update the variables to the desired values
  • continue with the actual script

When running the above example I get

Microsoft.CodeAnalysis.Scripting.CompilationErrorException: “(1,1): error CS0656: Missing compiler required member ‘Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation’”

which is in Microsoft.CSharp.dll Did this happen to anyone else?

Any update on this issue, or is it still on the backlog? edit: solved our needs with the example below

// for sending in parameters to the script
public class Globals
{
    public dynamic data;
}

// Script that will use dynamic
var scriptContent = "data.X + data.Y";

// data to be sent into the script
dynamic expando = new ExpandoObject();
expando.X = 34;
expando.Y = 45;

// setup references needed
var refs = new List<MetadataReference>{
    MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).GetTypeInfo().Assembly.Location),
    MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.DynamicAttribute).GetTypeInfo().Assembly.Location)};
var script = CSharpScript.Create(scriptContent, options: ScriptOptions.Default.AddReferences(refs), globalsType: typeof(Globals));

script.Compile();

// create new global that will contain the data we want to send into the script
var g = new Globals() { data = expando };

//Execute and display result
var r = script.RunAsync(g).Result;
Console.WriteLine(r.ReturnValue);

No update, it’s still on backlog.

An alternative workaround would be to add an indirection:

var script = CSharpScript.Create("dyn.a + dyn.b");
...
script.Run(new { dyn = expando })

The cause of this not being supported can be found in https://github.com/dotnet/roslyn/blob/03e30451ce7eb518e364b5806c524623424103e4/src/Scripting/Core/ScriptVariables.cs#L98 where it finds the fields and properties of the given instance and converts it into a map of script variables.

If this could check if the object is an IDictionary<String, Object> object, then my scenario could be supported quite easily (in my opinion, at least).

Or am I completely off-base? I don’t know the codebase well-enough to actually be confident.