Net 編譯器平臺--- Roslyn Scripting APIs

2023-07-08 21:01:04

引言

上一篇中.Net 編譯器平臺 --- Roslyn,介紹了Roslyn的各項功能,包括公開API,使用語法,使用語意,使用工作區等功能。

那麼回到上一篇中提到的問題,實現類似這樣的功能(以下程式碼為虛擬碼):

string scriptText = "int a = 1;int b = 2; return a+b ;";

var result = Script.Run(scriptText);

就用到了上一篇提到的 Scripting APIs,還是先了解一下Roslyn提供的 Scripting APIs 有哪些。

官方檔案(https://github.com/dotnet/roslyn/blob/main/docs/wiki/Scripting-API-Samples.md) 還是英文版,還是先將他翻譯為中文,以下內容為譯文。

Scripting APIs Samples

指令碼 API 可以讓 .NET 應用程式範例化一個 C# 引擎,並針對由宿主提供的物件執行程式碼片段。以下是使用指令碼 API 並進行一些常見範例的入門範例。您也可以檢視指令碼 API 的原始碼。

請注意,作為一個語言模型,我無法提供實時的原始碼範例或連結到具體的原始碼。但是,您可以參考 Microsoft 的官方檔案和範例來了解如何使用指令碼 API 並檢視相關原始碼。

支援的平臺

指令碼 API 需要桌面版 .NET Framework 4.6+ 或 .NET Core 1.1(自 Roslyn v2.0.0-rc3、Visual Studio 2017 RC3 起支援)。

指令碼 API 無法在通用 Windows 應用程式和 .NET Native 中使用,因為應用程式模型不支援在執行時載入生成的程式碼。

開始準備

安裝 Scripting API NuGet 包:

Install-Package Microsoft.CodeAnalysis.CSharp.Scripting

範例程式碼

以下範例程式碼中需要新增參照 using Microsoft.CodeAnalysis.CSharp.Scripting;

應用場景:

  • 評估一個C#表示式(Evaluate a C# expression)
  • 評估一個C#表示式(強型別)(Evaluate a C# expression(strongly-typed))
  • 帶錯誤處理的評估C#表示式(Evaluated a C# expression with error handling)
  • 新增參照(Add references)
  • 新增名稱空間和型別匯入(Add namespace and type imports)
  • 為指令碼引數化(Parameterize a script)
  • 建立和構建一個C#指令碼,並多次執行(Create&build a C# script and execute it multiple times)
  • 建立一個指向指令碼的委託(Create a delegate to a script)
  • 執行一個C#程式碼片段並檢查定義的指令碼變數(Run a C# snippet and inspect defined script variables)
  • 將程式碼片段連結成一個指令碼(Chain code snippets to form a script)
  • 從先前狀態繼續執行指令碼(Continue script execution from a previous state)
  • 建立和分析一個C#指令碼(Create and analyze a C# script)
  • 自定義程式集載入(Customize assembly loading)

評估一個C#表示式(Evaluate a C# expression)

object result = await CSharpScript.EvaluateAsync("1 + 2");

評估一個C#表示式(強型別)(Evaluate a C# expression(strongly-typed))

int result = await CSharpScript.EvaluateAsync<int>("1 + 2");

帶錯誤處理的評估C#表示式(Evaluated a C# expression with error handling)

try
{
    Console.WriteLine(await CSharpScript.EvaluateAsync("2+2"));
}
catch (CompilationErrorException e)
{
    Console.WriteLine(string.Join(Environment.NewLine, e.Diagnostics));
}

新增參照(Add references)

var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()", 
ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly)); 

新增名稱空間和型別匯入(Add namespace and type imports)

在下面的程式碼中,WithImports("System.IO") using System.IO; 新增到指令碼選項中,使得可以在指令碼程式碼中直接參照 System.IO 名稱空間的型別,而無需使用限定符。

var result = await CSharpScript.EvaluateAsync("Directory.GetCurrentDirectory()"), 
                                ScriptOptions.Default.WithImports("System.IO"));

同樣地,WithImports("System.Math")using static System.Math; 新增到指令碼選項中,使得可以在指令碼程式碼中直接參照 System.Math 型別的成員,而無需使用限定符。

var result = await CSharpScript.EvaluateAsync("Sqrt(2)", 
                                ScriptOptions.Default.WithImports("System.Math"));

為指令碼引數化(Parameterize a script)

public class Globals
{
    public int X;
    public int Y;
}

var globals = new Globals { X = 1, Y = 2 };

Console.WriteLine(await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals));

:::tip{title="提示"}
目前,Globals 型別必須在從檔案載入的程式集中定義。如果程式集在記憶體中(包括在互動式視窗中執行範例時),指令碼將無法存取該型別。請參閱此處的問題。
:::

建立和構建一個C#指令碼,並多次執行(Create&build a C# script and execute it multiple times)

var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));

script.Compile();

for (int i = 0; i < 10; i++)
{
    Console.WriteLine((await script.RunAsync(new Globals { X = i, Y = i })).ReturnValue);
} 

建立一個指令碼的委託(Create a delegate to a script)

該委託不會保持編譯資源(語法樹等)處於活動狀態。

var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));

ScriptRunner<int> runner = script.CreateDelegate();

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(await runner(new Globals { X = i, Y = i }));
} 

執行一個C#程式碼片段並檢查定義的指令碼變數(Run a C# snippet and inspect defined script variables)

var state = await CSharpScript.RunAsync<int>("int answer = 42;");

foreach (var variable in state.Variables)
     Console.WriteLine($"{variable.Name} = {variable.Value} of type {variable.Type}");

將程式碼片段連結成一個指令碼(Chain code snippets to form a script)

var script = CSharpScript.
            Create<int>("int x = 1;").
            ContinueWith("int y = 2;").
            ContinueWith("x + y");

Console.WriteLine((await script.RunAsync()).ReturnValue); 

從先前狀態繼續執行指令碼(Continue script execution from a previous state)

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);

建立和分析一個C#指令碼(Create and analyze a C# script)

using Microsoft.CodeAnalysis;

var script = CSharpScript.Create<int>("3");
Compilation compilation = script.GetCompilation();
//do stuff

編譯(Compilation)提供了對完整的 Roslyn API 集合的存取。

自定義程式集載入(Customize assembly loading)

using Microsoft.CodeAnalysis.Scripting.Hosting;

using (var loader = new InteractiveAssemblyLoader())
{
    var script = CSharpScript.Create<int>("1", assemblyLoader: loader);
    //do stuff 
}

參考

https://github.com/dotnet/roslyn/blob/main/docs/wiki/Scripting-API-Samples.md