Using Roslyn to build a simple C# interactive script engine

The .NET Compiler Platform (a.k.a. Roslyn) version 1.0-rc2 was recently released and got go-live license which means the API’s are to be considered fairly stable. In this post I’ll demonstrate how to build a simple C# interactive scripting* editor using the Scripting API (like the F# interactive window which been around in Visual Studio since…like forever). The idea is to be able to evaluate some code in a REPL but also have the possibility to submit code to it from an editor. Let’s start building the core component of the application and then put on a nice GUI on top of it.

The simple script engine looks like this:

This code utilizes the Microsoft.CodeAnalysis.CSharp.Scripting package available at NuGet.
The CSharpScript.RunAsync method runs a C# script while scriptState.ContinueWithAsync continues the previously submitted code with the current submitted one.
With this in place we can try out the script engine:

Let’s now put a simple editor and a REPL on top of the script engine. The REPL will be a self-hosted Web API console application to which the user can submit code from the editor but also write expressions in the command prompt. The setup of the REPL project is fairly straight-forward and I will only show the interesting parts for brevity (no error handling etc). The Main function sets up the Web API part and then listen for input from the user while the Post method in the controller listens for submitted code from the editor:

The code editor in this case is a simple Windows Form application with a multiline text box as the editor window.
When the user has written a script she selects it and presses Alt + Enter and the code is submitted to the REPL application:

The code editor can now be used to run the scripts as shown above:
ScriptEditor
REPL

Conclusion

Roslyn makes it really easy to build tools around C#/VB.NET. In this post I have shown how you, with just a few lines of code, can build an interactive C# script application.
The complete application code is available here.
Learn more about Roslyn on its Github repo.

* The scripting API’s are not yet available in the v1.0-rc2 “Go-Live” release and aren’t available on NuGet. They can however be fetched from the Rosyln myget feed

This Post Has 17 Comments

  1. Hi Christian,

    Thanks for the post.
    I’m trying to get your sample running but I can’t find “Microsoft.CodeAnalysis.Scripting” package on nuguet?

    Kind Regards,
    Dustyn

    1. Hi Dustyn,
      My bad, the scripting API’s aren’t included in the main release but are still actively being investigated by the Roslyn team. You can still download the scripting API’s by adding https://www.myget.org/F/roslyn-nightly/ to your NuGet package sources (Tools/NuGet Package Manager/Package Sources). I’ll update the post.
      Thanks

  2. I was trying to download Microsoft.CodeAnalysis.Scripting version 1.1.0-rc1-20151014-15 (Prerelease Last Published: 10/15/2015) but got error ”
    Unable to resolve dependency ‘System.Reflection.Metadata (≥ 1.1.0-beta-23413)'”. Is there a way to have someone publish the required System.Reflection.Metadata version?

    1. Hi Wenning,
      I don’t know the reason for the error. Did you try to run the sample in the blog (source available here)? It uses version 1.0.0-rc2 of the scripting package and should work.

  3. Thank you for a great post. Unfortunately there seems like there has been some breaking changes, so that I can not get it to run. This line “var script = CSharpScript.Create(code, ScriptOptions.Default).WithPrevious(_previousInput);” fails because WithPrevious does not exist anymore. Do you know how to update the code to get it running?

    1. Thanks for your comment Roger!
      I don’t know how to update the code to get it running unfortunately but I will do some investigation!

  4. I did some repo diving, and…

    WithPrevious changes to ContinueWith: https://github.com/dotnet/roslyn/commit/c655c2ccaa48270ee46012c523ebdd5c29965fc3

    Run changes to RunAsync:
    https://github.com/dotnet/roslyn/commit/083258058150b83646fa3fc60ba0cd55c4d88eb5

    I also removed your _nextInputState variable because Roslyn now requires its type to be specified and I wasn’t sure what to tell it.

    And the String type is no longer available without a using declaration, so I changed it to string.

    The result, which runs just fine:

    public class CSharpScriptEngine
    {
    private static Script _previousInput;

    public static object Execute(string code)
    {
    Script script;
    if (_previousInput == null)
    { script = CSharpScript.Create(code); }
    else
    { script = _previousInput.ContinueWith(code); }

    var endState = script.RunAsync().Result;
    _previousInput = endState.Script;
    return endState.ReturnValue;
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    CSharpScriptEngine.Execute(
    //This could be code submitted from the editor
    @”
    public class ScriptedClass
    {
    public string HelloWorld {get;set;}
    public ScriptedClass()
    {
    HelloWorld = “”Hello Roslyn!””;
    }
    }”);
    //And this from the REPL
    Console.WriteLine(CSharpScriptEngine.Execute(“new ScriptedClass().HelloWorld”));
    Console.ReadKey();
    }
    }

    1. Great find! I’ll update the post.
      Thanks!

  5. Well, I’m using this code with very few changes and it’s pretty robust. I need to add some actual code analysis for my project, but the REPL doesn’t need many changes. This is the only place I’ve found how to do this, so thanks!

    I found a small gotcha: The following code (which is a static version of what I use now) will trigger a very strange AccessViolationException about not being able to access System.RuntimeType. tl;dr Roslyn doesn’t work well with anonymous types. That might seem obvious, but it bit me so I think it’s worth stating.

    public class CSharpScriptEngine
    {
    private static Script _previousInput;

    private static object _globals = new {};

    public static AggregateException Exception { get; private set; }

    public static object Execute(string code)
    {
    Script script;
    if (_previousInput == null)
    { script = CSharpScript.Create(code, null, _globals.GetType()); }
    else
    { script = _previousInput.ContinueWith(code); }

    var task = script.RunAsync(_globals);
    task.Wait();

    Exception = task.Exception;

    if (Exception == null)
    {
    var endState = task.Result;
    _previousInput = endState.Script;
    return endState.ReturnValue;
    }
    else
    { return null; }
    }
    }

    1. Thanks for this, awesome! :)

  6. I was also getting an AccessViolationException
    I found the problem was private static object _globals = new {};

    If you use a real class the problem goes away:

    class Globals{ public int X = 1; }
    private static Globals _globals = new Globals();

    1. Nice, thanks!

  7. Finally got around to fix the issues with the script engine code. In order to make it work I needed to upgrade to .NET 4.6.1 and switch package Microsoft.CodeAnalysis.Scripting.CSharp with Microsoft.CodeAnalysis.CSharp.Scripting. See above for the final version of the CSharpScriptEngine class.

  8. I Could not get the code working. CSharpScriptEngine.Execute gives an error stating it could not load the System.Runtime. Do you have a version of this code working with the latest Rosslyn

    1. Hi Andy,

      I had some issues with the latest version in the repo which I’ve fixed. They were not about issues loading System.Runtime though. Are you using Visual Studio 2013? If so, try running it with Visual Studio 2015 instead if you have it available.

Leave a Reply

Close Menu