Roslyn 是 C# 和 Visual Basic 編譯器的開源實現,能夠動態的編譯和執行程式碼,可以利用他實現類似於 Vue 在 HTML 中的樣板語法:直接在 HTML 的 {{}} 中使用 JavaScript 語法。
而我們透過 Roslyn 則可以在程式執行時使用 C# 語法執行計算,非常靈活。
需要注意的是使用的 C# 和 .NET 版本取決於套件版本,這裡列出常見的套件版本與 C#, .NET 版本對應表:
錯誤範例:
錯誤訊息:
如果沒有指定 System.IO 的話會出現下面的錯誤
參考資料:
Github - dotnet/roslyn
Github - dotnet/roslyn Wiki NuGet-packages
Github - dotnet/roslyn Wiki Scripting-API-Samples
而我們透過 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
留言
張貼留言
如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com