Skip to content

Instantly share code, notes, and snippets.

@TheSnowfield
Last active February 14, 2024 04:56
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save TheSnowfield/2c52641d58e73ade1df2447c15f48683 to your computer and use it in GitHub Desktop.
Save TheSnowfield/2c52641d58e73ade1df2447c15f48683 to your computer and use it in GitHub Desktop.
Dynamically run C# code in the same context

Why we cannot use ExpandoObject

C# is not the thing what we thought about like the JavaScript, JavaScript has a global context object for the script running.
But in c# is not so easy (at least the runtime doesn't offer us that function directly).

But, how does csi.exe did it?

csi.exe initializes a CommandLineRunner for command interactive while starting. Then create a global context ScriptState<object> to save the execution results(return value, variables, methods). Once you commit the code to the terminal, it appends your code to the global context and compiles the code, and runs again or shows you the error.

But, some private and internal methods or classes prevent you to do like csi.exe do (ScriptBuilder, CSharpScriptCompiler, Script.CreateInitialScript).

### About the Black Magic It only does two things:

  • Initialize the environment for the first time you create the sandbox.
  • Grab the internal compiler from runtime.

The update: You now can go without using the black magic.

Use the sandbox

Make sure you have installed Roslyn nuget package.

<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0-3.final" />

then type the code

var sandbox = new SandBox();
{
    dynamic result = await sandbox.RunAsync("return \"Hello world!\"");
    Console.WriteLine(result);
}

You will get a "Hello world!" string output if no exception happens.

public class SandBox
{
private ScriptState<object> _globalState;
public SandBox()
{
// Create initial script
var script = CSharpScript.Create(string.Empty, ScriptOptions.Default);
{
// Create an empty state
_globalState = script.RunAsync
(null, _ => true, default).GetAwaiter().GetResult();
}
}
public async Task<object> RunAsync(string code, CancellationToken token = default)
{
// Append the code to the last session
var newScript = _globalState.Script
.ContinueWith(code, ScriptOptions.Default);
// Diagnostics
var diagnostics = newScript.Compile(token);
foreach (var item in diagnostics)
{
if (item.Severity >= DiagnosticSeverity.Error) return null;
}
// Execute the code
_globalState = await newScript
.RunFromAsync(_globalState, _ => true, token);
return _globalState.ReturnValue;
}
}
@F-Unction
Copy link

Dependencies:

Install-Package Microsoft.CodeAnalysis.CSharp.Scripting
Install-Package Microsoft.CodeAnalysis.Scripting.Common

@PhotonSPK
Copy link

https://gist.github.com/AkulaKirov/dbfd77132bd563dcd69e269ad64b45fe
A fix version for error that GlobalTypes not functional for the script.
replace optionsOpt into globalsTypeOpt at BlackInteractiveMagic.cs , Line 30.

@TheSnowfield
Copy link
Author

@AkulaKirov I fixed the issue you have noticed. hearty thanks.

@ParaN3xus
Copy link

if (item.Severity > DiagnosticSeverity.Error) return null; is useless because the highest severity level is Error.

@TheSnowfield
Copy link
Author

if (item.Severity > DiagnosticSeverity.Error) return null; is useless because the highest severity level is Error.

Oh, I see. I've updated the sample code. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment