C# 程式碼動態編譯和執行

Roslyn 是 C# 和 Visual Basic 編譯器的開源實現,能夠動態的編譯和執行程式碼,可以利用他實現類似於 Vue 在 HTML 中的樣板語法:直接在 HTML 的 {{}} 中使用 JavaScript 語法。
而我們透過 Roslyn 則可以在程式執行時使用 C# 語法執行計算,非常靈活。

安裝

先使用 NuGet 安裝 Microsoft.CodeAnalysis.CSharp.Scripting 套件,或是使用 .NET CLI 執行以下指令安裝
	
dotnet add package Microsoft.CodeAnalysis.CSharp.Scripting
    

需要注意的是使用的 C# 和 .NET 版本取決於套件版本,這裡列出常見的套件版本與 C#, .NET 版本對應表:
  • 3.7: C# 8.0, .NET Core 3.1
  • 3.11: C# 9.0, .NET 5
  • 4.3.1: C# 10.0, .NET 6
  • 4.6: C# 11.0, .NET 7

程式碼示範

動態執行,不指定回傳型別

    
using Microsoft.CodeAnalysis.CSharp.Scripting;

object result = await CSharpScript.EvaluateAsync("1 + 2");
Console.WriteLine($"結果: {result}"); // 結果: 3
    

動態執行,指定回傳型別

    
using Microsoft.CodeAnalysis.CSharp.Scripting;

int result = await CSharpScript.EvaluateAsync<int>("1 + 2");
Console.WriteLine($"結果: {result}"); // 結果: 3
    

錯誤範例:
    
using Microsoft.CodeAnalysis.CSharp.Scripting;

object result = await CSharpScript.EvaluateAsync("a + 2");
Console.WriteLine($"結果: {result}");
    

錯誤訊息:
    
Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,1): error CS0103: 名稱 'a' 不存在於目前的內容中
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, DiagnosticFormatter formatter)
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, Boolean emitDebugInformation, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.GetExecutor(CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\2023\ConsoleApp\ConsoleApp\Program.cs:line 17
   at Program.<Main>(String[] args)
Process finished with exit code -532,462,766.
    

捕捉錯誤,回傳簡易錯誤訊息

這應該會是最常見的使用方式了,因為通常會讓使用者查看的訊息不會給予多麼詳細(複雜)的回應方式
    
try
{
    Console.WriteLine(await CSharpScript.EvaluateAsync("a+2"));
}
catch (CompilationErrorException e)
{
    Console.WriteLine(string.Join(Environment.NewLine, e.Diagnostics));
    // (1,1): error CS0103: 名稱 'a' 不存在於目前的內容中
}
    

執行程式碼區塊

    
using Microsoft.CodeAnalysis.CSharp.Scripting;

string code = @"
    int a = 1;
    int b = 2;
    int c = a + b;
    return c;
";

object result = await CSharpScript.EvaluateAsync(code);
Console.WriteLine($"結果: {result}"); // 結果: 3
    

使用命名空間

這裡就需要小心使用,因為是動態執行,如果能夠讓使用者輸入,會有許多風險,非必要不要讓使用者輸入和使用命名空間。
    
string code = @"
    Directory.GetCurrentDirectory()
";

ScriptOptions scriptOptions = ScriptOptions.Default
    .AddImports("System.IO");
    
object result = await CSharpScript.EvaluateAsync(code, scriptOptions);
Console.WriteLine($"結果: {result}");
// 結果: C:\Users\ruyut\Documents\RiderProjects\2023\ConsoleApp\ConsoleApp\bin\Debug\net6.0
    

如果沒有指定 System.IO 的話會出現下面的錯誤
    
Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (2,5): error CS0103: 名稱 'Directory' 不存在於目前的內容中
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, DiagnosticFormatter formatter)
   at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, Boolean emitDebugInformation, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.GetExecutor(CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1.RunAsync(Object globals, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync[T](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\2023\ConsoleApp\ConsoleApp\Program.cs:line 15
   at Program.<Main>(String[] args)

Process finished with exit code -532,462,766.
    



參考資料:
Github - dotnet/roslyn
Github - dotnet/roslyn Wiki NuGet-packages
Github - dotnet/roslyn Wiki Scripting-API-Samples

留言