CSharpScript
是 Roslyn 编译器提供的脚本 API,允许在运行时动态执行 C# 代码。以下是其核心原理和典型应用场景:
一、核心原理
1. 架构基础
-
基于 Roslyn 编译器即服务(Compiler as a Service)
-
将代码文本实时编译为内存中的程序集
-
通过反射或轻量级代码生成执行
2. 执行流程
源代码 → 语法分析 → 绑定 → 编译 → 内存程序集 → 执行
3. 关键技术
-
增量编译:重复执行时复用部分编译结果
-
脚本会话:通过
ScriptState
保持执行上下文 -
对象传递:通过
globals
参数实现宿主与脚本的数据交换
二、基础应用
1. 基本执行
var result = await CSharpScript.EvaluateAsync("1 + 2");
Console.WriteLine(result); // 输出 3
2. 带上下文执行
public class Globals { public int X = 1, Y = 2; }
var globals = new Globals();
var result = await CSharpScript.EvaluateAsync<int>("X + Y",
globals: globals);
3. 多步执行
var state = await CSharpScript.RunAsync("int x = 1;");
state = await state.ContinueWithAsync("int y = 2;");
state = await state.ContinueWithAsync("x + y");
Console.WriteLine(state.ReturnValue); // 输出 3
三、高级特性
1. 自定义引用和导入
var options = ScriptOptions.Default
.WithReferences(typeof(DateTime).Assembly)
.WithImports("System.Math");
var result = await CSharpScript.EvaluateAsync<double>("Sqrt(4)", options);
2. 异常处理
try
{
await CSharpScript.EvaluateAsync("throw new Exception(\"Test\");");
}
catch (CompilationErrorException e)
{
Console.WriteLine(string.Join("\n", e.Diagnostics));
}
3. 性能优化
// 预编译脚本
var script = CSharpScript.Create<int>("X * Y", globalsType: typeof(Globals));
var compiled = script.Compile();
// 重复执行时使用编译结果
for (int i = 0; i < 100; i++)
{
var result = await script.RunAsync(new Globals { X = i, Y = i });
}
四、典型应用场景
1. 动态规则引擎
var rule = "input.Age > 18 && input.Score > 60";
var result = await CSharpScript.EvaluateAsync<bool>(rule,
globals: new { input = new { Age = 20, Score = 70 } });
2. 公式计算器
var formula = "Math.Sin(x) + Math.Cos(y)";
var options = ScriptOptions.Default.WithImports("System.Math");
var calculate = ScriptOptions.Default
.WithReferences(typeof(Math).Assembly);
var result = await CSharpScript.EvaluateAsync<double>(
formula, globals: new { x = 1.0, y = 2.0 }, options);
3. 插件系统
string pluginCode = """
using PluginBase;
public class MyPlugin : IPlugin {
public string Execute() => "Hello from plugin";
}
return new MyPlugin();
""";
var options = ScriptOptions.Default
.WithReferences(typeof(IPlugin).Assembly)
.WithImports("PluginBase");
var plugin = await CSharpScript.EvaluateAsync<IPlugin>(pluginCode, options);
Console.WriteLine(plugin.Execute());
五、性能注意事项
-
编译开销:首次执行较慢(约100-500ms)
-
内存占用:每个脚本会生成内存程序集
-
最佳实践:
-
预编译高频使用的脚本
-
复用
ScriptOptions
实例 -
避免在循环中动态编译
-
六、安全限制
-
代码访问控制:
var options = ScriptOptions.Default
.WithReferences(/* 白名单程序集 */);
-
沙箱方案:
AppDomain sandbox = AppDomain.CreateDomain("Sandbox");
try {
// 在沙箱中执行脚本
}
finally {
AppDomain.Unload(sandbox);
}
七、Helloworld
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
public class TestController
{
public List<string> Output { get; set; } = new List<string>(); // 改为可读写
}
class Program
{
static async Task<int> Main(string[] args)
{
var controller = new TestController();
var remainingCodeTxt = """
Output.Add("Hello,world!");
return Output;
""";
try
{
// 执行脚本
var result = await CSharpScript.EvaluateAsync<List<string>>(
remainingCodeTxt,
globals: controller,
options: ScriptOptions.Default
.WithImports("System.Collections.Generic")
.WithReferences(typeof(List<string>).Assembly));
Console.WriteLine("Controller.Output: " + string.Join(", ", controller.Output));
Console.WriteLine("脚本返回值: " + string.Join(", ", result));
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 1;
}
}
}
CSharpScript
为 C# 提供了强大的运行时代码执行能力,适用于需要动态性的场景,但需注意性能和安全性问题。