CSharpScript Using Externally Defined Types - Can't Convert From Type A to A

1.9k Views Asked by At

Problem: Can't use externally defined types in CSharpScript because it can't convert the object type from itself to itself due to some Assembly mismatch I guess.

I have 2 projects.

Common

using System;

namespace Common
{
    public class Arguments
    {
        public string Text;
    }

    public class Output
    {
        public bool Success;
    }
}

and

CSharpScriptingExperiment

using System;
using System.Collections.Generic;
using Common;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class Parameters
{
    public string text;

    public Arguments arguments;
}

namespace CSharpScriptingExperiment
{
    class Program
    {
        static void Main(string[] args)
        {   
            ScriptOptions options = ScriptOptions.Default.WithImports(new List<string>() { "Common" });

            options = options.AddReferences(typeof(Arguments).Assembly);

            // Script will compare the text inside arguments object to the text passed in via function parameters
            var script = CSharpScript.Create(@"
                public class TestClass
                {
                    public Output DoSomething(string text, Arguments args)
                    {
                        return new Output() { Success = args.Text == text };
                    }
                }", options: options, globalsType: typeof(Parameters));

            var nextStep = script.ContinueWith<object>("return new TestClass().DoSomething(text, arguments);");

            // Setup the global paramters object
            Parameters parameters = new Parameters();
            parameters.text = "Hello";
            parameters.arguments = new Arguments()
            {
                Text = "Hello"
            };

            // Run script
            Output output = (Output)nextStep.RunAsync(globals: parameters).Result.ReturnValue;

            Console.WriteLine(output.Success);
            Console.ReadLine();
        }
    }
}

When I run CSharpScriptingExperiment I get this error:

"(1,42): error CS1503: Argument 2: cannot convert from 'Common.Arguments [/Users/username/Projects/CSharpScriptingExperiment/CSharpScriptingExperiment/bin/Debug/netcoreapp2.2/Common.dll]' to 'Common.Arguments [Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]'"

On this line:

Output output = (Output)nextStep.RunAsync(globals: parameters).Result.ReturnValue;

Common is a .NET Standard 2.0 project.

CSharpScriptingExperiment is a .NET Core 2.2 project.

Any ideas? I've seen other people coming across similar issues but not found a solution.

1

There are 1 best solutions below

0
On BEST ANSWER

Got it working with a slight workaround.

There are 2 distinct scopes when it comes to CSharpScript. One is the normal C# scope for code inside functions, classes etc. The second is a unique feature to CSharpScript which is the static scope. It allows variables and code to be run without being in a class or a function - kind of like a REPL.

The problem is, when an external type object is loaded into the static scope, which then in turn is supposed to be passed into a function which accepts an argument of that type, the object representation from the static scope and normal scope are incompatbile. It's that intermediate static scope which causes the problem.

So it goes like this:

Executing Assembly -> Script Static Scope -> Script Normal Scope

And this causes the issue above.

When doing this:

Executing Assembly -> Script Normal Scope

or

Script Normal Scope -> Executing Assembly

Things work fine.

So I can return an external type object from the function to the executing assembly, but can't pass an external type object into the function by going through the static scope first.

The workaround is to accept an object in the function and then cast the object to the external type inside of the function.

using System;
using System.Collections.Generic;
using Common;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class Parameters
{
    public string text;

    public Arguments arguments;
}

namespace CSharpScriptingExperiment
{
    class Program
    {
        static void Main(string[] args)
        {   
            ScriptOptions options = ScriptOptions.Default.WithImports(new List<string>() { "Common" });

            options = options.AddReferences(typeof(Arguments).Assembly);

            // Script will compare the text inside arguments object to the text passed in via function parameters
            var script = CSharpScript.Create(@"
                public class TestClass
                {
                    public Output DoSomething(string text, object arguments)
                    {
                        Arguments args = (Arguments)arguments;
                        return new Output() { Success = args.Text == text };
                    }
                }", options: options, globalsType: typeof(Parameters));

            var nextStep = script.ContinueWith<object>("return new TestClass().DoSomething(text, arguments);");

            // Setup the global paramters object
            Parameters parameters = new Parameters();
            parameters.text = "Hello";
            parameters.arguments = new Arguments()
            {
                Text = "Hello"
            };

            // Run script
            Output output = (Output)nextStep.RunAsync(globals: parameters).Result.ReturnValue;

            Console.WriteLine(output.Success);
            Console.ReadLine();
        }
    }
}

So the core takeaway is that as long as the object doesn't pass through the static scope of the way, things work as expected. If the object needs to pass through the static scope, deal with it as an object and cast inside of the target function to whatever it needs to be - the scripting engine seems to have issues doing the casting itself and the types are fundamentally conflicting.

This is based purely on black box testing and debugging - I would love the Roslyn team to weigh in on this or someone who has worked on the internals to check if my intuition and findings are correct.

Hope it helps anyone else who stumbles across this!