Learn Roslyn Now: Part 14 Intro to the Scripting API

The Scripting API is finally here! After being removed from Roslyn’s 1.0 release it’s now available (for C#) in pre-release format on NuGet. To install to your project just run:

Install-Package Microsoft.CodeAnalysis.Scripting -Pre

Note: You need to target .NET 4.6 or you’ll get the following exception when running your scripts:

Could not load file or assembly 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

Note: Today (October 15, 2015) the Scripting APIs depend on the 1.1.0-beta1 release, so you’ll have to update your Microsoft.CodeAnalysis references to match if you want to use all of Roslyn with the scripting stuff.

There are a few different ways to use the Scripting API.

EvaluateAsync

CSharpScript.EvaluateAsync is probably the simplest way to get started evaluating expressions. Simple pass any expression that would return a single result to this method it will be evaluated for you.


var result = await CSharpScript.EvaluateAsync("5 + 5");
Console.WriteLine(result); // 10
result = await CSharpScript.EvaluateAsync(@"""sample""");
Console.WriteLine(result); // sample
result = await CSharpScript.EvaluateAsync(@"""sample"" + "" string""");
Console.WriteLine(result); // sample string
result = await CSharpScript.EvaluateAsync("int x = 5; int y = 5; x"); //Note the last x is not contained in a proper statement
Console.WriteLine(result); // 5

RunAsync

Not every script returns a single value. For more complex scripts we may want to keep track of state or inspect different variables. CSharpScript.RunAsync creates and returns a ScriptState object that allows us to do exactly this. Take a look:


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""");
ScriptVariable x = state.Variables["x"];
ScriptVariable y = state.Variables["y"];
Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 5
Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 3

view raw

RunAsync1.cs

hosted with ❤ by GitHub

We can also maintain the state of our script and continue applying changes to it with ScriptState.ContinueWith():


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""").Result;
state = state.ContinueWithAsync("x++; y = 1;").Result;
state = state.ContinueWithAsync("x = x + y;").Result;
ScriptVariable x = state.Variables["x"];
ScriptVariable y = state.Variables["y"];
Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 7
Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 1

view raw

ContinueWith.cs

hosted with ❤ by GitHub

ScriptOptions

We can start to get into more interesting code by adding references to DLLs that we’d like to use. We use ScriptOptions to provide out script with the proper MetadataReferences.


ScriptOptions scriptOptions = ScriptOptions.Default;
//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Linq.Enumerable).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore);
//Add namespaces
scriptOptions = scriptOptions.AddNamespaces("System");
scriptOptions = scriptOptions.AddNamespaces("System.Linq");
scriptOptions = scriptOptions.AddNamespaces("System.Collections.Generic");
var state = await CSharpScript.RunAsync(@"var x = new List(){1,2,3,4,5};", scriptOptions);
state = await state.ContinueWithAsync("var y = x.Take(3).ToList();");
var y = state.Variables["y"];
var yList = (List)y.Value;
foreach(var val in yList)
{
Console.Write(val + " "); // Prints 1 2 3
}

This stuff is surprisingly broad. The Microsoft.CodeAnalysis.Scripting namespace is full of public types that I’m not at all familiar with and there’s a lot left to learn. I’m excited to see what people will build with this and how they might be able to incorporate scripting into their applications.

Kasey Uhlenhuth from the Roslyn team has compiled a list of code snippets to help get you off the ground with the Scripting API. Check them out on GitHub!

If you’ve got some cool plans for the scripting API, let me know if the comments below!

12 thoughts on “Learn Roslyn Now: Part 14 Intro to the Scripting API

  1. I keep getting the Error Severity Code Description Project File Line Suppression State
    Error CS1061 ‘ScriptOptions’ does not contain a definition for ‘AddNamespace’ and no extension method ‘AddNamespace’ accepting a first argument of type ‘ScriptOptions’ could be found (are you missing a using directive or an assembly reference?) testcXML C:\Users\bbabb\Documents\Visual Studio 2013\Projects\KMBScXML\testcXML\Program.cs 56 Active

  2. Hey Josh, I raised a question on the StackOverflow
    (http://stackoverflow.com/questions/41464734/how-to-pause-or-stop-csharpscript-running/41471198#41471198) as following

    I’m using Roslyn’s scripting API in my application, code snippet as following:

    public class ScriptEngine
    {
    public static string CodeText;
    public static event Action CompileErrorEvent;

    public static async Task RunScriptAsync(CancellationToken ct)
    {
    try
    {
    var scriptResult = await CSharpScript.RunAsync(CodeText, null, new ScriptHost(), null, ct);
    return true;
    }
    catch (Microsoft.CodeAnalysis.Scripting.CompilationErrorException ex)
    {
    List result = new List();

    foreach (var item in ex.Diagnostics)
    {
    result.Add(item.ToString());
    }

    if (result.Count > 0)
    {
    CompileErrorEvent?.Invoke(result.ToArray());
    }

    return false;
    }
    catch (Exception ex)
    {
    IMCP_Base.Dialog.Show.SimpleError(“脚本运行”, ex.Message, “修改脚本”);
    return false;
    }
    }
    …….
    }

    public static CancellationTokenSource ScriptCTS;

    private async void btnScriptRun_ItemClick(object sender, ItemClickEventArgs e)
    {
    ScriptCTS = new CancellationTokenSource();

    if (CheckScriptEditorIsNotNull())
    {
    Script.ScriptEngine.CodeText = ScriptEditor.GetCode();
    bool runSuccess = await Script.ScriptEngine.RunScriptAsync(ScriptCTS.Token);
    }
    }

    private void btnScriptStop_ItemClick(object sender, ItemClickEventArgs e)
    {
    ScriptCTS?.Cancel();
    }
    CSharpScript.RunAsync method runs well, but when I click ScriptStop button, ScriptCTS?.Cancel() can’t cancel running script.

    How can I stop or pause a script running?

  3. I had to make some edits to the second code sample to get it to work.

    1. The `RunAsync` argument needed two more double-quotes added at the end (otherwise it resulted in CS1010).
    2. The calls to `state.Variables[…];` had to be changed to `state.Result.Variables.Single(v => v.Name == …);`

Leave a reply to joshvarty Cancel reply