Executing dynamic C# code at runtime can be powerful but also comes with security and performance risks. Microsoft’s Roslyn compiler provides a way to compile and execute C# code dynamically while offering safety mechanisms.
This guide walks through how to use Roslyn to safely evaluate and run C# code at runtime.
Why Use Roslyn for Dynamic Code Execution?
Roslyn enables runtime compilation of C# code, making it useful for:
- Scripting engines within applications.
- Plugins and extensibility without recompiling the main application.
- Interactive debugging and testing scenarios.
- Custom formula evaluations in applications like rule engines.
Step 1: Install Roslyn Dependencies
To use Roslyn for dynamic execution, install the necessary NuGet packages:
Install-Package Microsoft.CodeAnalysis.CSharp.Scripting
Install-Package Microsoft.CodeAnalysis.Scripting
Step 2: Basic Execution of Dynamic Code
A simple way to execute dynamic C# code using Roslyn:
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
class Program
{
static async Task Main()
{
string code = "1 + 2";
var result = await CSharpScript.EvaluateAsync<int>(code);
Console.WriteLine("Result: " + result);
}
}
Step 3: Providing Context for Execution
To allow dynamic scripts to use variables and functions from your main program, use a custom script state:
class ScriptGlobals
{
public int X { get; set; } = 10;
}
var options = ScriptOptions.Default.AddReferences(typeof(ScriptGlobals).Assembly);
string code = "X * 2";
var result = await CSharpScript.EvaluateAsync<int>(code, options, new ScriptGlobals());
Console.WriteLine(result); // Output: 20
Step 4: Handling Exceptions in Dynamic Code
Since executing untrusted code can lead to runtime errors, wrap execution in try-catch:
try
{
string invalidCode = "int x = 1 / 0;";
await CSharpScript.EvaluateAsync(invalidCode);
}
catch (CompilationErrorException ex)
{
Console.WriteLine("Compilation Error: " + string.Join("\n", ex.Diagnostics));
}
catch (Exception ex)
{
Console.WriteLine("Runtime Error: " + ex.Message);
}
Step 5: Security Considerations
Executing user-provided code can be risky. Follow these best practices:
1. Use a Restricted Execution Context
Limit the namespaces and APIs available to the script:
var options = ScriptOptions.Default
.AddReferences(typeof(object).Assembly) // Only essential assemblies
.WithImports("System"); // Restrict available namespaces
2. Limit Execution Time
Run code in a separate thread with a timeout:
using System.Threading;
using System.Threading.Tasks;
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
var task = CSharpScript.EvaluateAsync("while(true) {}", cancellationToken: cts.Token);
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Execution Timed Out");
}
3. Use AppDomain Sandboxing (For Older .NET Versions)
In older .NET Framework applications, AppDomains can be used to isolate script execution. However, .NET Core and later versions no longer support AppDomains.
Step 6: Running More Complex Scripts with State
For multi-line scripts, use RunAsync
instead of EvaluateAsync
:
string script = @"
int Multiply(int a, int b) => a * b;
return Multiply(3, 4);
";
var result = await CSharpScript.RunAsync(script);
Console.WriteLine(result.ReturnValue); // Output: 12
Conclusion
Roslyn provides a powerful way to execute C# code dynamically while maintaining security and control. By following best practices such as limiting execution scope, handling errors, and enforcing timeouts, you can safely integrate dynamic scripting into your applications without exposing them to excessive risk.