C# CSharpScript 的原理与应用
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# 提供了强大的运行时代码执行能力,适用于需要动态性的场景,但需注意性能和安全性问题。