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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
We can also maintain the state of our script and continue applying changes to it with ScriptState.ContinueWith()
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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
.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!
Previously (as in 2011) one of the major features of Roslyn-based scripting was that the script executes in a sandbox. Is this still true?
Unfortunately I don’t think that’s still the case, or at least it hasn’t been highlighted or mentioned anywhere that I could find.
Hey Josh, your Blog really helped me understand Roslyn, many google searches ended me up here!
The coolest usage of scripting I could come up with is scripting against the Roslyn API itself from within Visual Studio.
Check it out at http://blog.frankbakker.net/2015/11/find-code-patterns-using-c-and-roslyn.html
That’s a pretty cool idea! Awesome to see people putting the C# Scripting stuff to good use 🙂
Hey Josh, how we can we reach to you via e-mail ?
My email is available in the “About” section of my blog: https://joshvarty.wordpress.com/about/
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
AddImports worked for me, e.g:
scriptOptions = scriptOptions.AddImports(“System.Linq”);
This is pretty cool. If you wanted to expose this to the internet, how would you secure the API surface area? I have raised a GitHub issue discussing this here https://github.com/dotnet/roslyn/issues/10830.
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?
Can you give me some advice about this.
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 == …);`