20210910のC#に関する記事は5件です。

C#からSpreadSheetの読み書きをする(2021年9月版)

【GCPでの手続き】 GCPでの手続き ①GCPプロジェクトを作成する こちらにアクセス 以下のどちらかの画面が出るので、進行してプロジェクトを作成する ②認証情報に進む ③使用するAPIを指定する ※上記画面への進み方が分からなくなった場合 GCPにアクセス ④OAuth同意画面の設定 ⑤スコープを追加する ⑥OAuthクライアントID設定 ⑦credentialのダウンロード ⑧アプリを公開する 【スプレッドシートの準備】 スプレッドシートの準備 ①スプレッドシートの用意 コチラからスプレッドシートを新規作成する シートに、値を適当に入れておく ②スプレッドシートの公開とID取得 【VisualStudioでのプロジェクト準備】 VisualStudioでのプロジェクト準備 ①VisualStudioにて、Nugetパッケージマネージャーを開く ②パッケージをインストール パッケージマネージャーにて下記を入力してEnter Install-Package Google.Apis.Sheets.v4 「Google.Apis.Sheets.v4 1.55.0.2371' が xxxxxxxx に正常にインストールされました」と表示されたらOK ③credential.jsonの配置 GCP手続きの最後にダウンロードしたファイルをcredential.jsonにリネームする VisualStudioにて、ソリューションエクスプローラーを開く credential.jsonをD&Dで追加する もしくは、ファイルを選んで追加する ④credential.jsonのプロパティ設定 プロパティを開き「常にコピーする」に変更 【C#ソースコード_SpreadSheetからの読み取り編】 C#ソースコード_SpreadSheetからの読み取り編 ①VisualStudioにて、ソースコードをコピペ 以下をコピペする ※ソースコードの冒頭の、SpreadsheetIdに、先ほど作成したSpreadSheetのIDを入力する URLの「d/」と「/edit」に挟まれた部分 https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxx/edit#gid=0 using Google.Apis.Auth.OAuth2; using Google.Apis.Sheets.v4; using Google.Apis.Sheets.v4.Data; using Google.Apis.Services; using Google.Apis.Util.Store; using System; using System.Collections.Generic; using System.IO; using System.Threading; class Program { // https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxx/edit#gid=0 const string SpreadsheetId = "xxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxx"; static void Main(string[] args) { // SheetsServiceを作成. SheetsService service = GetSheetsService(); // 通信リクエストを作成. var request = service.Spreadsheets.Values.Get(SpreadsheetId,"シート1!1:1000"); // 通信の同期実行. レスポンスを受け取る. ValueRange response = request.Execute(); IList<IList<object>> values = response.Values; for (int i = 0;i < values.Count;i++) { for (int k = 0;k < values[i].Count;k++) { Console.WriteLine($"({i},{k}) = " + values[i][k]); } } Console.Read(); } static SheetsService GetSheetsService() { var initializer = new BaseClientService.Initializer(); initializer.HttpClientInitializer = GetUserCredential(); initializer.ApplicationName = "test"; var sheetsService = new SheetsService(initializer); return sheetsService; } static UserCredential GetUserCredential() { ClientSecrets clientSecrets; using (var stream = new FileStream("credentials.json",FileMode.Open,FileAccess.Read)) { clientSecrets = GoogleClientSecrets.FromStream(stream).Secrets; } // Googleの認証通信開始. UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync( clientSecrets, new string[] { SheetsService.Scope.Spreadsheets }, "user", CancellationToken.None, new FileDataStore("token.json",true)).Result; return credential; } } ②実行する 実行すると、Googleアカウントのログインが求められるので、GCPで使用したアカウントでログインする その後、警告が表示されるが、左下の詳細をクリック xxxに移動をクリック ③コンソールにて表示された事を確認! 【C#ソースコード_SpreadSheetへの書き出し編】 C#ソースコード_SpreadSheetへの書き出し編 ①Main関数の置き換え Main関数を以下に置き換える static void Main(string[] args) { // 書き込む値. var values = new List<IList<object>>() { // 1行目に書き込む値. new List<object> { 1,2,3,4,5, }, // 2行目に書き込む値. new List<object> { "6","7","8","9","10", }, // 3行目に書き込む値. new List<object> { DateTime.Now } }; // リクエストボディを作成. var requestBody = new ValueRange() { Values = values }; // SheetsServiceを作成. SheetsService service = GetSheetsService(); // 通信リクエストを作成. var request = service.Spreadsheets.Values.Update(requestBody,SpreadsheetId,"シート1!1:1000"); request.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED; // 通信実行. var result = request.Execute(); } ②SpreadSheetに値が反映された事を確認!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NETCoreでアクション別のバージョン管理

アクション別のバージョン管理 「Microsoft.AspNetCore.Mvc.Versioning」を使用したバージョン管理方法 参考 手順 バージョン管理を行いたいプロジェクトに Microsoft.AspNetCore.Mvc.Versioning を追加 Install-Package Microsoft.AspNetCore.Mvc.Versioning Startup.cs の ConfigureServices()に以下を追加 Startup.cs public void ConfigureServices(IServiceCollection services) { // ApiVersioningを追加 services.AddApiVersioning(options => { // クライアントにApiバージョンを通知 options.ReportApiVersions = true; // これがないとクライアント側でエラーが出る options.AssumeDefaultVersionWhenUnspecified = true; // Apiのデフォルトバージョンを1.0に設定 options.DefaultApiVersion = new ApiVersion(1, 0); }); services.AddControllers(); } 3.Controllerの名前空間及び関数に属性を追加 HomeController.cs // サポートしているバージョンを追加 [ApiVersion("1.0")] [ApiVersion("2.0")] [Route("v{version:apiVersion}/[controller]")] public class HomeController : Controller { [HttpGet] public string Get() => "v1"; // Apiバージョンを上書き [HttpGet, MapToApiVersion("2.0")] public string GetV2() => "v2"; } /v1/Home v1が表示 /v2/Home v2が表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsサービスをC#で作る

はじめに Visual Studio のテンプレートで基本部分はすぐ作れるものの、その後に色々あるのでメモ。 プロジェクトの作成 ここでは Visual Studio 2019 で作ってみる。 まず、新しいプロジェクトの作成で、「Windows サービス(.NET Framework)」を作る。 .NET Frameworkのバージョンは多分どれでも良いが、とりあえずここでは4.5にする。少なくとも4.5なら、今のWindows10では初期状態でも動くっぽい。(.NET Frameworkの手動インストール不要) 名前をつける デフォルトでService1.csというファイルが作られる。 この名前がサービス名になってしまうので、まずはこれを変える。 ファイル名を変更すると、関連するところを修正するか聞かれるので、OKで自動修正する。 ファイル名の変更では変わらないところが1箇所(<サービス名>.cs[デザイン]タブのプロパティにある ServiceName)あるので、これも変更しておく。 インストーラの追加 サービスとして登録できるようにするために、インストーラを追加する必要がある。 <サービス名>.cs[デザイン]タブ で右クリックし、メニューから「インストーラーの追加」を選択する。 ProjectInstaller.cs ファイルが追加される。 ProjectInstaller.cs[デザイン] タブに「ServiceInstaller1」の項目があるので、右クリックのプロパティにある「Description」にサービスの説明を書いておく。(services.mscで表示される説明文) 「DisplayName」にはサービス名(services.mscで表示されるサービス名)を書いておく。 また、StartTypeがデフォルトで「Manual」なので、「Automatic」にするとWindows起動時に自動起動するようになる。 サービス実行アカウントの指定 ProjectInstaller.cs[デザイン] タブに「serviceProcessInstaller1」の項目があるので、右クリックのプロパティを開き、「Account」を変えることでサービス実行アカウントを選択する。 一般的には「LocalSystem」にすることが多い(イベントログの書き込み権限など、許可されていることが多いので)ようだが、必要な権限で決めるべき。 いわゆる管理者権限が必要ならば「LocalSystem」にする。 必要ない場合は「LocalService」で良いかもしれない。 サービスのインストールについて サービスはインストールすることでWindowsに登録され、services.mscに表示されるようになる。 インストール方法は、 installutil を使う scを使う InstallShieldやmsiの指定でインストールする サービスのプログラムを実行することでインストールする といった方法がある。 ここでは、サービスのプログラムを実行することでインストールする方法を説明する。 やり方は簡単で、ManagedInstallerClass.InstallHelper を呼び出せば良い。 具体的には以下のようになる。 using System.Configuration.Install; using System.Reflection; . . . try {     ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location }); } catch (Exception e) {     Log.Write("install error: " + e.ToString()); } これは毎回呼ぶわけにいかないので、コマンドライン引数で指定されたときに呼び出すようにする。 コマンドラインで実行しても、Console.Write ではなにも出力されないようなので注意。 static class Program {     static void Main(string[] args)     { if (0 < args.Length) { string arg = args[0].ToLower().Trim(); if (arg.Equals("/i")) { InstallService(); } else if (arg.Equals("/u")) { UninstallService(); } return; } // 以下に元々の Main ・ ・ ・ }     private static void InstallService()     {         try         {             ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });         }         catch (Exception e)         {     Log.Write("install error: " + e.ToString());         }     }     private static void UninstallService()     {         try         {             ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });         }         catch (Exception e)         {     Log.Write("uninstall error: " + e.ToString());         }     } } これで、管理者権限で起動したコマンドプロンプトで、<サービス名>.exe /i と実行するとサービスが登録される。 アンインストールは /i の代わりに /u を使う。 サービスの実行 <サービス名>.exe /i で登録しておけば、あとは通常のサービスと同様に実行できる。 service.msc を起動して「サービスの開始」か、コマンドプロンプトで net start "<サービス名>"で実行する。 net start で指定するサービス名は、ServiceInstaller1 の DisplayName で指定したものにする サービス本体の処理 サービス開始時に呼び出される OnStart と停止時に呼び出される OnStop を使う。OnStart, OnStopはテンプレートに含まれているので、自動的に作られる。 OnStartで別スレッドを開始するか、System.Timers.Timer を使って処理を実装する。 OnStopでスレッドやタイマーを終了する。 OnStart, OnStop に時間がかかる処理を入れる場合は、SetServiceStatus(https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-setservicestatus) を使って保留状態を追加すると良いが、ここでは省略。 サービスから画面に通知メッセージを表示する サービスでWindowやDialogの表示はそのままではできない。これはサービスを実行しているアカウントが、画面に表示する権限を持たないため。 このため、ログオン中のユーザ権限を使って CreateProcessAsUser し、通知用の別アプリを起動する。 そもそもサービスは誰もログオンしていなくても動いているので、OnSessionChangeを使って、ログオンしたら別アプリを起動、ログオフでアプリを終了し、プロセス間通信を使ってメッセージなどを表示させる。 OnSessionChange ユーザがログオン・ログオフ時に呼び出され、そこで session ID が得られるのでCreateProcessAsUserが実行できる。 OnSessionChange自体は普通にoverrideすれば良いが、CanHandleSessionChangeEvent = true; を実行しておく必要がある。 これはサービスのクラスのコンストラクタなどに書けばOK。 OnSessionChangeの例は以下の通り。 protected override void OnSessionChange(SessionChangeDescription changeDescription) { switch (changeDescription.Reason) { case SessionChangeReason.SessionLogon: startApplication(changeDescription.SessionId); break; case SessionChangeReason.SessionLogoff: stopApplication(); break; } } void startApplication(int sesid) で通知メッセージ表示のアプリを起動する。 stopApplicationで終了。 アプリ起動 アプリの起動は CreateProcessAsUserで行うが、その前にトークンを取得する必要がある。 トークンは WTSQueryUserToken で取得する。 この辺りのAPIはNativeMethodsにまとめるが、構造体が必要なのでちょっと面倒。 とりあえずまとめるとこんな感じ。 class NativeMethods { [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public int cb; public String lpReserved; public String lpDesktop; public String lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [DllImport("wtsapi32.dll", SetLastError = true)] public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token); [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; public static Int32 WTS_CURRENT_SESSION = -1; } この定義をした上で、startApplicationは以下のようになる。 private void startApplication(int sesid) { IntPtr token = IntPtr.Zero; bool res = NativeMethods.WTSQueryUserToken((uint)sesid, out token); if (!res) { Log.Write("WTSQueryUserToken failed: " + Marshal.GetLastWin32Error()); return; } string path = "C:\\temp\\servicemessage.exe"; // メッセージ表示の実行ファイル string dir = "C:\\temp"; // 実行時のディレクトリ var si = new NativeMethods.STARTUPINFO(); si.lpDesktop = "winsta0\\default"; si.cb = Marshal.SizeOf(si); var pi = new NativeMethods.PROCESS_INFORMATION(); var sa = new NativeMethods.SECURITY_ATTRIBUTES(); sa.bInheritHandle = false; sa.Length = Marshal.SizeOf(sa); sa.lpSecurityDescriptor = IntPtr.Zero; res = NativeMethods.CreateProcessAsUser(token, path, string.Empty, ref sa, ref sa, false, 0, IntPtr.Zero, dir, ref si, out pi); if (!res) { Log.Write("CreateProcessAsUser failed: " + Marshal.GetLastWin32Error()); } } ちゃんとやるなら実行ファイルのパスは Assembly.GetExecutingAssembly().Location とかから作るのが良さそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プリザンターをVisual Studioでデバッグする

概要 この記事では、オープンソースのローコード開発プラットフォーム「Pleasanter」をVisual Studioでデバッグする際の手順を記載しています。 事前準備 下記の手順に従いWindowsにプリザンターをインストールして起動するのを確認します。 https://pleasanter.org/manual/getting-started-pleasanter-windows Visual Studio Community 2019のインストール 下記よりVisual Studio Community 2019をダウンロードしてインストールします。 https://visualstudio.microsoft.com/ja/vs/community/ ソースコードのクローン Visual Studioを起動しリポジトリのクローンをクリックします。 リポジトリの場所に下記のURLを入力しクローンボタンをクリックします。 https://github.com/Implem/Implem.Pleasanter.NetCore ソリューションファイルをダブルクリックします。 下図のようにプロジェクトファイルの一覧が表示されればOKです。 データベース接続の設定 事前準備でインストールしたRds.jsonをコピーしてクローンしたディレクトリのRds.jsonを上書きコピーします。コピー元のファイルは標準では下記のフォルダに存在します。 C:\web\pleasanter\Implem.Pleasanter\App_Data\Parameters\Rds.json 下図のようにVisual Studio上で下記変わっていることを確認します。 スタートアッププロジェクトの設定 ソリューションエクスプローラーで「Implem.Pleasanter.NetCore」を右クリックして「スタートアッププロジェクトに設定」をクリックします。下図のように「Implem.Pleasanter.NetCore」が太字になればOKです。 デバッグ実行 デバッグ実行ボタンをクリックします。 下図のようにデバッグモードでプリザンターが起動します。事前準備でインストールしたプリザンターのデータベースに接続しています。 下記のようにItemsController.csを開いてIndexメソッドにブレークポイントを設置し、デバッグモードで起動したブラウザでログイン操作を行います。 下図のようにブレークポイントで停止し、変数の中身などが確認できるようになりました。エラーが発生した際の原因調査や、独自機能のカスタマイズなど様々な用途に活用できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSharpScriptでスクリプトファイル実行機能を実装する

はじめに C#のスクリプト実行用コードが公開されてから6年以上が経ってるんですね. パッケージ公開からも6年近く(?)ということで, だいぶネット上に日本語の情報が出てきていますが, どこのページも「動かし方」は書いてあっても詳しいところまでは触れられてなさげに感じます(失礼 私自身, ちょいちょい躓いたところがあったので, 改めてまとめて記事にしてみました ちなみに, Visual Basicのスクリプト実行用実装(VisualBasicScript)も同じタイミングでソースが公開されており, CSharpScriptと同じような感覚で使用することができそうに見えます… が, NuGet.orgではUnlisted Packageになっていますし, まだ機能として使用可能な状態ではないんですかね. 用語解説 私の知識が間違っているかもしれませんが, この投稿では特定の語句の意味を次のように解釈して作成しています. 語句 意味 スクリプト C#で書かれた処理(+ コメント等)で構成された文字列のこと スクリプト実行 スクリプトに含まれる処理が意味する処理を実行すること スクリプトファイル スクリプトだけが記録されたファイルのこと スクリプト実行機能を実装するには 色々なところで触れられていますが, ここにも一応書いておきます 1. nugetパッケージを参照する プロジェクトにMicrosoft.CodeAnalysis.CSharp.Scriptingパッケージを追加します 最低要件が.NET Standard 2.0なので, .NET Framework4.6.1以上 あるいは .NET Core 2.0以上等で使用できます ( 参考 : Microsoft Docs ".NET Standard" ) 依存しているパッケージが色々あるので, パッケージの追加には時間がかかるかもしれません 2. スクリプトを読み込むコードを追加する (CSharpScriptクラス) 実際にスクリプトを読み込む処理は, Microsoft.CodeAnalysis.CSharp.ScriptingnamespaceのCSharpScriptクラスにあるstaticメソッド群を用いて行うのが比較的楽です. Docsにメソッドの一覧とかあればよかったんですが, 軽く見た感じ無さそう… ということで, 実装のほうから見てみましょう. こちら, RoslynリポジトリのCSharpScript.csです. 上記リンクは, 記事を書いた時点でmainブランチにあった最新のcommitでの状態を参照しています. 以降も同じタイミングのものを参照していきます. このファイルにはCSharpScriptクラスが実装されており, partialキーワードが無いのでCSharpScriptクラスのメソッドはこのファイルにしか実装されていないことがわかります. で, ファイルを見てみると以下のstaticメソッドが含まれていることがわかります. Script<T> Create<T>(string code, ScriptOptions options = null, Type globalsType = null, InteractiveAssemblyLoader assemblyLoader = null) Script<T> Create<T>(Stream code, ScriptOptions options = null, Type globalsType = null, InteractiveAssemblyLoader assemblyLoader = null) Script<object> Create(string code, ScriptOptions options = null, Type globalsType = null, InteractiveAssemblyLoader assemblyLoader = null) Script<object> Create(Stream code, ScriptOptions options = null, Type globalsType = null, InteractiveAssemblyLoader assemblyLoader = null) Task<ScriptState<T>> RunAsync<T>(string code, ScriptOptions options = null, object globals = null, Type globalsType = null, CancellationToken cancellationToken = default(CancellationToken)) Task<ScriptState<object>> RunAsync(string code, ScriptOptions options = null, object globals = null, Type globalsType = null, CancellationToken cancellationToken = default(CancellationToken)) Task<T> EvaluateAsync<T>(string code, ScriptOptions options = null, object globals = null, Type globalsType = null, CancellationToken cancellationToken = default(CancellationToken)) Task<object> EvaluateAsync(string code, ScriptOptions options = null, object globals = null, Type globalsType = null, CancellationToken cancellationToken = default(CancellationToken)) 型引数をとらないメソッド(例:Create(...)メソッド)は, 同一名で型引数を取るメソッドの型引数に object を設定(例:Create<object>(...))して呼ぶ実装になっています. ということは, stringとStreamの違いを気にせずに考えると, Createメソッド, RunAsyncメソッド, EvaluateAsyncメソッドの3種類が用意されているわけです. 2.1 Createメソッド スクリプトを読みこんでオブジェクト化するメソッドです (コンパイルや実行はまだ行われていません) 同じスクリプトを何回も実行する場合はこれが適当です. 各Createメソッドは, 型引数を含めて引数を5つ取ります. T : 型引数. スクリプトの戻り値の型を指定する (デフォルトはobject) code : 実際のスクリプトのstringあるいはStreamを渡す options : ScriptOptions型で, スクリプトを解釈するオプション(コンパイルオプションのようなものと考えてOK)を指定できる (省略可) globalsType : Type型で, スクリプトにGlobal Object (Cとかでいう大域変数的な) として渡すオブジェクトの型 assemblyLoader : InteractiveAssemblyLoader型インスタンスを渡す. 詳細は後述します 戻り値はScript<T>型で, スクリプトを読み取った結果が格納されています. 詳細は後述します. 例として, 2 * 3という計算を行う処理をスクリプトとして入力する場合を考えます. この場合, 以下のような記述をすれば良いです. Script<int> createdScript = CSharpScript.Create<int>("return 2 * 3;"); このままでは「スクリプトを読んだ」だけの状態なため, まだ実行結果を受け取ることはできません. もう少し先で説明しますが, 実行するには上記の例の場合createdScript.RunAsync()ってすればOKです. 戻り値まで取得する場合は, RunAsyncから返ってきたインスタンスのReturnValueプロパティを参照する形になります. Global Objectを活用するともう少し楽になるので, そちらの説明もご参照ください. 2.2 RunAsyncメソッド スクリプトを解析してすぐに実行するメソッドです. あるスクリプトを1回だけ実行すればいいといった場合はこのメソッドが適当です. 各RunAsyncメソッドは, 型引数を含めて引数を6つ取ります. T : 型引数. スクリプトの戻り値の型を指定する (デフォルトはobject) code : 実際のスクリプトのstringあるいはStreamを渡す options : ScriptOptions型で, スクリプトを解釈するオプション(コンパイルオプションのようなものと考えてOK)を指定できる (省略可) globals : object型で, Global Objectとして渡すインスタンスを渡す globalsType : Type型で, スクリプトにGlobal Object (Cとかでいう大域変数的な) として渡すオブジェクトの型 cancellationToken : CancellationToken型インスタンスを渡す このRunAsyncメソッド, 内部的にはCreateメソッドを呼んで, 返ってきたScript<T>型インスタンスでRunAsyncメソッドを実行しているだけです. Script<T>.RunAsyncメソッドについての詳細は後述します. 2.3 EvaluateAsyncメソッド スクリプトを解析してすぐに実行し, 戻り値を取得するメソッドです. スクリプト実行後は戻り値さえ取得できればOKといった場合にはこのメソッドが適当です. 引数についてはRunAsyncメソッドと同じなため紹介を省略します. 戻り値はTask<T>型なので, 次のような感じで使用することになります. int result = await CSharpScript.EvaluateAsync<int>("return 2 * 3;"); このEvaluateAsyncメソッド, 内部ではCSharpScript.RunAsyncメソッドを実行して, 返ってきたTask<ScriptState<T>>型インスタンスのEvaluateAsyncメソッドを実行しているだけ… なんですが, 実はScriptState<T>.EvaluateAsyncメソッドのアクセス修飾子はinternalでして… とはいえ, ScriptState<T>.EvaluateAsyncメソッドは処理的にはScriptState<T>.ReturnValueを返してるだけなので, そこまで気にする必要はないです. 例えば, このEvaluateAsyncメソッドの代わりにRunAsyncメソッドを利用する場合は, 次のようなコードを書けばOKです. // resultには 2 * 3 の結果 (= 6) が入る int result = (await CSharpScript.RunAsync<int>("2 * 3")).ReturnValue; 2.4 Global Objectについて スクリプトと呼び出し側の間でデータのやり取りを行えるのが Global Object です. 例えば, 次のようなコードを書いたとします. Sample.cs (50行近くあるので畳んでいます) Sample.cs using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.CSharp.Scripting; using System; using System.Threading.Tasks; const string ResultVariableName = "result"; //Create ScriptObject Script<int>? createdScript = CSharpScript.Create<int>( // Script $"int {ResultVariableName} = Result = A * B; return {ResultVariableName};", //ScriptOptions null, //Type of GlobalObject typeof(Hogehoge) ); Console.WriteLine("== Compile =========="); //Compile var compileResult = createdScript.Compile(); //Print CompileLog (Diagnostics) foreach (var item in compileResult) Console.WriteLine(item); Console.WriteLine("== Diagnostics Print End =========="); //Prepare Global Object Hogehoge globalObject = new() { A = 2, B = 3 }; //Run var runResult = await createdScript.RunAsync(globalObject); //Print Result Console.WriteLine($"globalObject \t: {globalObject.A} * {globalObject.B} = {globalObject.Result}"); Console.WriteLine($"Var in Script \t: {globalObject.A} * {globalObject.B} = {runResult?.GetVariable(ResultVariableName).Value}"); Console.WriteLine($"From runResult\t: {globalObject.A} * {globalObject.B} = {runResult?.ReturnValue}"); public class Hogehoge { public int A { get; init; } public int B { get; init; } public int Result { get; set; } } C#9.0から追加された "最上位レベルのステートメント" 機能を使っているので, すっきりしているようなしていないような…? まぁそれはおいといて. このコードを dotnet run で実行すると, 次のような出力を得ることができます. output == Compile ========== == Diagnostics Print End ========== globalObject : 2 * 3 = 6 Var in Script : 2 * 3 = 6 From runResult : 2 * 3 = 6 こんな風に, 事前に指定した型のインスタンスを渡すことで, スクリプト内でその型のパブリックメンバにアクセスできます (この書き方はちょっと正確ではないんですが, まぁまぁ) 個人的には "スクリプト内でそのインスタンスに対するメンバーアクセス式を書く際は, 一番最初に出てくる. トークンとその左が要らなくなる…というイメージでいますが, これで伝わるんですかね…? new Object().Equals(null); でいうと new Object().の部分です 上の説明で理解できた方はわかるかと思いますが, 注意してほしいのは, スクリプトがその型の中で実行されるわけじゃないという点です. 例えば, 先ほどのSample.csのHogehogeクラスを次のように修正してみたとします. Sample_2.cs public class Hogehoge { public int A { get; init; } internal int B { get; init; } public int Result { get; private set; } } Bプロパティのアクセス修飾子をinternalにして, Resultプロパティのsetterのアクセス修飾子をprivateに変更してみました. これを実行すると, 次のようにエラーが出力されます (さすがにUnhandled exceptionのパス出力部分は一部カットしました) output == Compile ========== (1,14): error CS0200: Property or indexer 'Hogehoge.Result' cannot be assigned to -- it is read only (1,27): error CS0103: The name 'B' does not exist in the current context == Diagnostics Print End ========== Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,14): error CS0200: Property or indexer 'Hogehoge.Result' cannot be assigned to -- it is read only 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 <Program>$.<<Main>$>d__0.MoveNext() in /.../Sample_2.cs:line 35 --- End of stack trace from previous location --- at <Program>$.<Main>(String[] args) ということで, スクリプトはクラス/アセンブリの外からアクセスされていることがわかります. ちなみに, 「スクリプトはクラス/アセンブリの外からアクセスされている」と書いていますが, Global Objectに指定する型のアクセス修飾子もpublicである必要があります. 仮にクラスをprivateにしてしまうと, 次のように怒られます. output Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,14): error CS0122: 'GlobalObject.A' はアクセスできない保護レベルになっています ちなみに, 今回はプロパティしか使用していませんが, さきほど "パブリックメンバにアクセスできる" と書いた通り, メソッドやフィールド等にもアクセス可能です. ちなみにちなみに, 前のほうで new Object().Equals(null); でいうと new Object().の部分 を省略するイメージと書きましたが, そこを省略しているだけという関係上, 継承元のメソッドも実行できます. まぁ当たり前と言えば当たり前なんですが. ものは試しに, 自作のクラスをGlobal Objectに設定したうえで, そのクラスのObject型から継承してきたメソッドをスクリプト内で実行してみようと思います. すべてのクラスはObject型を継承していますからね. Sample_3.cs (50行近くあるので畳んでいます) Sample_3.cs using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.CSharp.Scripting; using System; using System.Threading.Tasks; const string ResultVariableName = "result"; //Prepare Global Object Hogehoge globalObject = new() { A = 2, B = 3 }; const string scriptString = $@"using System; int {ResultVariableName} = Result = A * B; Console.WriteLine(Equals(abc)); Console.WriteLine(GetHashCode()); Console.WriteLine(ToString()); Console.WriteLine(GetType()); return {ResultVariableName}; "; //Create ScriptObject ScriptState<Tint>? runResult = await CSharpScript.RunAsync<int>( // Script scriptString, //ScriptOptions null, //GlobalObject globalObject ); //Print Result Console.WriteLine($"globalObject \t: {globalObject.A} * {globalObject.B} = {globalObject.Result}"); Console.WriteLine($"Var in Script \t: {globalObject.A} * {globalObject.B} = {runResult?.GetVariable(ResultVariableName).Value}"); Console.WriteLine($"From runResult\t: {globalObject.A} * {globalObject.B} = {runResult?.ReturnValue}"); public class Hogehoge { public int A { get; init; } public int B { get; init; } public int Result { get; set; } public static Hogehoge abc = new(); } Sample_2.csから色々変えてますが, やっていることは同じです. 実行結果は次のようになります output False 55467050 Hogehoge Hogehoge globalObject : 2 * 3 = 6 Var in Script : 2 * 3 = 6 From runResult : 2 * 3 = 6 正常に実行できていますね 2.5 おまけ : スクリプト内での this キーワード Microsoft DocsのC#リファレンスには, this キーワードについて, 次のように説明されています. this キーワードはクラスの現在のインスタンスを参照します。拡張メソッドの最初のパラメーターの修飾子としても使用されます。 (引用元 : this キーワード - C# リファレンス | Microsoft Docs) では, スクリプトで this キーワードを使って "現在のインスタンス" を取得しようとした場合, 何を得られるのでしょうか? 試しに, 次のようなコードを実行してみます. Sample_4.cs await Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync( // Script "System.Console.WriteLine(this);", //ScriptOptions null, //GlobalObject new System.Object() ); さすがにGlobalObjectとして渡したObject型インスタンスを参照するはずもなく. だからといって新しくクラスが作成されているとも思えず. 実行してみると… output Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,26): error CS0027: Keyword 'this' is not available in the current context 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.RunAsync(String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken) at <Program>$.<<Main>$>d__0.MoveNext() in /.../Sample_4.cs:line 1 --- End of stack trace from previous location --- at <Program>$.<Main>(String[] args) ということで, ここではthisキーワードを使えねぇよって返されることがわかりました. このことから, スクリプトはstaticメソッドのようなものと考えていいかもしれません. 3. スクリプトを実行するコードを書く (Script/Script<T>クラス) CSharpScript.RunAsyncメソッドやCSharpScript.EvaluateAsyncメソッドを用いた場合は戻り値としてスクリプトの実行結果が返ってきますが, CSharpScript.Createメソッドを用いた場合はスクリプトを読みこんだ結果しか返してくれません. ということで, スクリプトを実行するコードを書く必要があります. 簡単な答えを言ってしまうと "ScriptクラスのインスタンスメソッドであるRunAsyncメソッドを実行すればいい" だけですが, もう少し違う使い方もないか調べてみます. Createメソッドの戻り値はScript<T>型インスタンスです. なので, ScriptクラスのDocsを確認してみましょう. ScriptクラスはMicrosoft.CodeAnalysis.Scriptingnamespaceに属しています...が, Microsoft Docsの.NET API browserで検索しても出てきません ということで, CSharpScriptクラスのときと同じように, roslynリポジトリにあるソースコードからScriptクラスの中身を確認してみます. こちら, Script.csファイルです このファイルにはabstractであるScriptクラスと, Scriptクラスを継承したsealedなScript<T>クラスが実装されています. 見た感じinternalやprivateなメンバが多い印象がありますが, とりあえずScript<T>クラスからpublicかつ実行に関係ありそうなメソッドを抽出してみます. //Scriptクラスに実装されているメソッドで, Script<T>クラスでoverride等されていないもの Script<object> ContinueWith(string code, ScriptOptions options = null) Script<object> ContinueWith(Stream code, ScriptOptions options = null) Script<TResult> ContinueWith<TResult>(string code, ScriptOptions options = null) Script<TResult> ContinueWith<TResult>(Stream code, ScriptOptions options = null) ImmutableArray<Diagnostic> Compile(CancellationToken cancellationToken = default(CancellationToken)) //ここからScript<T> Task<ScriptState<T>> RunAsync(object globals, CancellationToken cancellationToken) Task<ScriptState<T>> RunAsync(object globals = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) ScriptRunner<T> CreateDelegate(CancellationToken cancellationToken = default(CancellationToken)) Task<ScriptState<T>> RunFromAsync(ScriptState previousState, CancellationToken cancellationToken) Task<ScriptState<T>> RunFromAsync(ScriptState previousState, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) さて, 色々ありますが… ContinueWith各メソッドについてはそこまで差がないので4つまとめて考えます, RunAsyncとRunFromAsyncメソッドのcatchExceptionを取らないやつはcatchExceptionを取るやつのcatchExceptionにnullを入れて呼んでるだけです. 以上のことを頭に入れたうえで, ContinueWith, Compile, RunAsync, CreateDelegate, RunFromAsyncの5つのメソッドについて紹介します. 3.1 ContinueWithメソッド 指定のスクリプトの前に現在のスクリプトを実行するように設定したScript型インスタンスを取得するメソッドです. インタラクティブな実行環境 / REPLを提供する際はこのメソッドが便利です. 例えば, 2 * 3の計算を実行してResultに入れたうえで, 別のスクリプトでそのResultを2乗するといった場合, 次のようなコードになります. Sample_5.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using System; const string scriptStr = "int A = 2;" + "int B = 3;" + "int Result = A * B;" + "System.Console.WriteLine($\"{A} * {B} = {Result}\");" + "return Result;"; Console.WriteLine("== script1 =="); Microsoft.CodeAnalysis.Scripting.ScriptState<object>? runResult_1 = await CSharpScript.RunAsync(scriptStr); Console.WriteLine("== script1 complete =="); var script2 = runResult_1.Script.ContinueWith<int>("Result * Result"); Console.WriteLine("== script2 =="); Microsoft.CodeAnalysis.Scripting.ScriptState<int>? runResult_2 = await script2.RunAsync(); Console.WriteLine("== script2 complete =="); Console.WriteLine(runResult_1.ReturnValue); Console.WriteLine(runResult_2.ReturnValue); 上記のコードを実行すると, 次のような出力を得ることができます. output == script1 == 2 * 3 = 6 == script1 complete == == script2 == 2 * 3 = 6 == script2 complete == 6 36 このように, script2.RunAsyncを行うと, Result * Resultの前に2 * 3 = 6の計算も行われています. ちなみに, 後で紹介するRunFromAsyncメソッドを用いると, runResult_1の実行結果を引き継いでResult * Resultの処理を行えます. 3.2 Compileメソッド スクリプトをコンパイルするメソッドです. このメソッドを呼ばなくても初回実行時に自動でコンパイルしてくれますが, このメソッドを事前に呼んでおくことで, 実行前に余計な時間をかけずに済みます. 引数であるCancellationToken型のcancellationTokenには, 操作を途中で取り消す際のために使用するトークンを渡します. …とか書いてますが, 私自身CancellationToken型を使用したことがなく… CancellationTokenについては公式のDocsをご参照ください. サンプルが載ってるのでわかりやすいです. 戻り値はImmutableArray<Diagnostic>型で, コンパイル結果が入っています. 具体的には, Warning以上のコンパイル結果が格納されています. この指定はハードコーディングされてしまっているので, Compileメソッドの実行でInformationレベルの情報を得ることはできません. まぁ不要っちゃ不要ではありますが. で. Issueも開いてますが, このCompileメソッドはコンパイルエラーが起きてもExceptionを吐きません. エラーが無かったか確認するには, 返ってきたDiagnostic配列にSeverity == DiagnosticSeverity.Errorなインスタンスが含まれていないことを確認する必要があります. ちなみに, コンパイルでエラーが出たやつをRunAsyncメソッド等で実行しようとすると, そっちでMicrosoft.CodeAnalysis.Scripting.CompilationErrorExceptionがthrowされるので, ご注意ください. …というか, 「Global Objectについて」のほうでそんな挙動をするって実例を出してましたね. こっちにも同じものを再掲します. output == Compile ========== (1,14): error CS0200: Property or indexer 'Hogehoge.Result' cannot be assigned to -- it is read only (1,27): error CS0103: The name 'B' does not exist in the current context == Diagnostics Print End ========== Unhandled exception. Microsoft.CodeAnalysis.Scripting.CompilationErrorException: (1,14): error CS0200: Property or indexer 'Hogehoge.Result' cannot be assigned to -- it is read only 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 <Program>$.<<Main>$>d__0.MoveNext() in /.../Sample_2.cs:line 35 --- End of stack trace from previous location --- at <Program>$.<Main>(String[] args) == Compile ====って出力の直後にCompileメソッドを呼んで, 返ってきた配列を出力して== Diagnostics Print End ====のあとにRunAsyncメソッドを実行しています (出力の=は一部省略しました) このように, コンパイルでエラーが起きてもExceptionはthrowされないことがわかります. 3.3 RunAsyncメソッド スクリプトを実行するメソッドです. スクリプトがコンパイルされていない場合, 自動でコンパイルが行われます. また, コンパイルされたスクリプトはキャッシュされるので, 次の実行の際にはコンパイル不要です. もっと言うと別のインスタンスでもキャッシュからコンパイル結果を取ってきてくれたりします(詳細未確認) RunAsyncメソッドが受ける引数は, 次の3つです. globals : object型で, Global Objectとして使用するインスタンスを渡します. 詳細は上の方にある"Global Objectについて"をご確認ください catchException : Func<Exception, bool>型で, スクリプトで発生した例外をcatchするために使用されます. 例外発生時にここで指定したメソッドが呼ばれるわけですが, このメソッドがtrueを返すと「例外処理済み」としてスクリプトの実行が継続されます. 逆に, falseを返すと「例外未処理」としてExceptionが再throwされて, RunAsyncメソッドのところまで投げられます. cancellationToken : 実行を中断するために使用するトークンです. 詳細はMicrosoft Docsをご参照ください. このメソッドの戻り値はTask<ScriptState<T>>型で, Task型のWrapを除くと, スクリプト内の変数等の実行結果が含まれています. 詳細は後述します. 3.4 CreateDelegateメソッド スクリプトを実行してスクリプトの戻り値を返すメソッドを取得するメソッドです. いや, 正確に言うと, 「スクリプトが意味する処理を実行するScriptRunner<T>型delegateを取得する」メソッドなんですが. ScriptRunner<T>型はここに定義されてまして, 要はGlobalObjectとCancellationTokenを渡してスクリプトの戻り値を受け取る非同期メソッドですね. 「何度も処理を実行し, 戻り値さえ取得できればいい」といった場合はこのメソッドでdelegateを取得すると便利です. コード例を次に示します. Sample_6.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; Script<int>? script = CSharpScript.Create<int>( // Script "int Result = Num_0 + Num_1;" + "(Num_0, Num_1) = (Num_1, Result);" + "return Result;", globalsType: typeof(GlobalObject) ); ScriptRunner<int> myFunc = script.CreateDelegate(); GlobalObject obj = new(); System.Console.WriteLine(obj.Num_0); System.Console.WriteLine(obj.Num_1); for (int i = 0; i < 10; i++) System.Console.WriteLine(await myFunc.Invoke(obj)); public class GlobalObject { public int Num_0 { get; set; } = 0; public int Num_1 { get; set; } = 1; } フィボナッチ数列を出力するプログラムです. 要らないとは思いますが, 出力は次のような感じになります. output 0 1 1 2 3 5 8 13 21 34 55 89 3.5 RunFromAsyncメソッド ContinueWithメソッドで連結したスクリプトを, 途中までの実行結果を引き継いで実行する際に使用します. 例として, 次のコードをご覧ください. Sample_7.cs using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; List<ScriptState<object>> scriptResults = new(); List<Script<object>> scripts = new(); scripts.Add(CSharpScript.Create("System.Console.Write(0); return 0;")); for (int i = 1; i < 10; i++) scripts.Add(scripts[i - 1].ContinueWith($"System.Console.Write({i}); return {i};")); foreach (var i in scripts) { scriptResults.Add(await i.RunAsync()); Console.WriteLine(); } Console.WriteLine("=== Prepare Complete ==="); Console.WriteLine($"\treturn : {(await RunFromTo(scriptResults, 2, 5)).ReturnValue}"); Console.WriteLine($"\treturn : {(await RunFromTo(scriptResults, 0, 9)).ReturnValue}"); Console.WriteLine($"\treturn : {(await RunFromTo(scriptResults, 8, 9)).ReturnValue}"); Console.WriteLine($"\treturn : {(await RunFromTo(scriptResults, 8, 8)).ReturnValue}"); static Task<ScriptState> RunFromTo(List<ScriptState<object>> scriptResults, int from, int to) { Console.WriteLine($"\n====== from:{from}, to:{to}"); return scriptResults[to].Script.RunFromAsync(scriptResults[from]); } このコードを実行すると, 次のような出力を得ることができます. output 0 01 012 0123 01234 012345 0123456 01234567 012345678 0123456789 === Prepare Complete === ====== from:2, to:5 345 return : 5 ====== from:0, to:9 123456789 return : 9 ====== from:8, to:9 9 return : 9 ====== from:8, to:8 return : 8 このように, ContinueWithメソッドで前に連結したスクリプトを実行する場合, RunAsyncメソッドで行うと一番初めのスクリプトから実行されてしまうのに対して, RunFromAsyncで任意の段階のスクリプト実行結果とともに実行してあげると, そこまでのスクリプトは実行せず次の段階から実行してくれます. 例えば, 上の出力でfrom:2, to:5という出力がありますが, これは「scripts[2]までの実行結果をもとに, script[5]までを順番に実行する」処理を行うことを意味しており, 実際にscript[2]以前の出力が無くscript[3]からscript[5]までしか実行されていないことがわかります. ちなみに, Script<T>.ContinueWith(...).RunFromAsync(...)をまとめたメソッドがありまして, それが後程紹介するScriptState<T>.ContinueWithAsyncメソッドです. 4. 実行結果を取り出すコードを書く (ScriptState/ScriptState<T>クラス) 利用したメソッドによってはダイレクトに戻り値を取得できているかもしれませんが, とりあえずここではCSharpScript.RunAsyncメソッドや, Script<T>.RunAsyncメソッド等の戻り値として得られるScriptState<T>型から, 目当ての情報を取得する方法を紹介します. このクラスもDocsがなさげなので, 実装を読んで紹介します. ScriptState<T>型は同じScriptState.csファイル内に実装されたScriptクラスを継承したクラスです. 基本的にScript<T>クラスで独自に実装されている機能は無いので, Scriptクラスに実装されたpublicメンバに注目します. Scriptクラスには, 次の9個のpublicメンバが含まれています(objectクラスから継承したメソッドを除く) Script Script { get; } Exception Exception { get; } object ReturnValue { get; } ImmutableArray<ScriptVariable> Variables { get; } ScriptVariable GetVariable(string name) Task<ScriptState<object>> ContinueWithAsync(string code, ScriptOptions options, CancellationToken cancellationToken) Task<ScriptState<object>> ContinueWithAsync(string code, ScriptOptions options = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) Task<ScriptState<TResult>> ContinueWithAsync<TResult>(string code, ScriptOptions options, CancellationToken cancellationToken) Task<ScriptState<TResult>> ContinueWithAsync<TResult>(string code, ScriptOptions options = null, Func<Exception, bool> catchException = null, CancellationToken cancellationToken = default(CancellationToken)) Script<T>クラスではReturnValueプロパティがT型だったりしますが, まぁそれは置いといて… 4.1 Scriptプロパティ このScriptState型インスタンスに対応するScript型インスタンスです. 言葉だけで説明されても…って方は, 次のコードを読むと理解できると思います. Sample_8.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; Script<object>? script = CSharpScript.Create("int.MaxValue"); ScriptState<object>? result_1 = await script.RunAsync(); System.Console.WriteLine(Equals(script, result_1.Script)); //True ScriptState<object>? result_2 = await result_1.ContinueWithAsync("int.MinValue"); System.Console.WriteLine(Equals(script, result_2.Script)); //False ScriptState<object>? result_3 = await CSharpScript.RunAsync("int.MaxValue"); System.Console.WriteLine(Equals(script, result_3.Script)); //False 4.2 Exceptionプロパティ スクリプト内でthrowされた例外です. throwされなければnull (正確にはdefaultだけど, 結果的にnullなのには変わりなし) が入ってます. 以下にサンプルコードを示します. Sample_9.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System; try { Script<object>? script = CSharpScript.Create("throw new System.Exception(\"Exception from Script\");"); ScriptState<object>? result = await script.RunAsync(catchException: ex => { System.Console.WriteLine($"~~~catchException~~~~~\n{ex}"); return true; }); Console.WriteLine("~~~result.Exception~~~~~"); Console.WriteLine(result?.Exception.ToString() ?? "null"); } catch(Exception ex) { Console.WriteLine("~~~caught Exception~~~~~"); Console.WriteLine(ex); } このコードの実行結果は次のとおりです. output ~~~catchException~~~~~ System.Exception: Exception from Script at Submission#0.<<Initialize>>d__0.MoveNext() --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken) ~~~result.Exception~~~~~ System.Exception: Exception from Script at Submission#0.<<Initialize>>d__0.MoveNext() --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken) 一旦catchExceptionに指定した処理でExceptionの発生を受け取ったものの, trueを返して「処理済み」扱いとしたので処理が継続されています. そのため, 無事に実行が完了し, result.Exceptionを参照することに成功しています. 4.3 ReturnValueプロパティ スクリプトの戻り値です. スクリプトに戻り値を返すコードが無かった場合, default(T)が記録されています. 以下にサンプルコードを示します. Sample_10.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System; using System.Threading.Tasks; ScriptState<object>[]? results = await Task.WhenAll( CSharpScript.RunAsync("1.2 * 2.3"), CSharpScript.RunAsync("2 * 3"), CSharpScript.RunAsync("new System.Object()"), CSharpScript.RunAsync("object obj;"), CSharpScript.RunAsync("int A = 0;") ); ScriptState<int>? result_1 = await CSharpScript.RunAsync<int>("int A = 0;"); foreach (var i in results) Console.WriteLine($"{i.ReturnValue?.GetType()?.ToString() ?? "null"} : {i.ReturnValue?.ToString() ?? "null"}"); Console.WriteLine($"{result_1.ReturnValue.GetType()?.ToString() ?? "null"} : {result_1.ReturnValue.ToString() ?? "null"}"); 上記コードの実行結果は次のとおりです. output System.Double : 2.76 System.Int32 : 6 System.Object : System.Object null : null null : null System.Int32 : 0 4.4 Variablesプロパティ スクリプト内のフィールドが記録された配列です. 以下の条件に合致するフィールドだけを取得できます. publicである とはいえ, スクリプト内でprivateなフィールドを作成することはできませんが フィールド名の長さが0よりも大きい とはいえ, フィールド名の長さを0にすることなんでできないとは思いますが フィールド名が「文字, 数字, _(アンダーバー)」のいずれかで始まっている 自動で生成された(<とか>とかそういうのを含む)フィールドは取得できません. 「文字, 数字」の判定はchar.IsLetterOrDigit(char)メソッドを利用して行われているので, 正確には「UnicodeカテゴリでUppercaseLetter, LowercaseLetter, TitlecaseLetter, ModifierLetter, OtherLetter, DecimalDigitNumberのいずれかに属する文字」+「_(アンダーバー)」だったりします (参照 : Char.IsLetterOrDigit Method (System) | Microsoft Docs) まぁ, 普通の使い方(?)をしていれば, スクリプト内で手動で定義したフィールドを取得できないなんてことはないと思います. 以下にサンプルコードを示します. Sample_11.cs const string scriptString = "int A = 123;" + "double B = 1.23;" + "static object obj = null;" + "const float constFloat = 2.34F;" + "readonly byte readonlyByte = 0x12;" + "short getonlyShort { get; } = 0x123;" + "string ABC { get; set; } = \"1 a 2 s 3\";" + "int fromShort = getonlyShort * 2;" + "private int @private = 999;"; var result = await Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync(scriptString); foreach (var i in result.Variables) System.Console.WriteLine($"{nameof(i.IsReadOnly)}:{i.IsReadOnly},\t{nameof(i.Name)}:{i.Name},\t{nameof(i.Type)}:{i.Type},\t{nameof(i.Value)}:{i.Value}"); 出力は次のとおりです. output IsReadOnly:False, Name:A, Type:System.Int32, Value:123 IsReadOnly:False, Name:B, Type:System.Double, Value:1.23 IsReadOnly:True, Name:readonlyByte, Type:System.Byte, Value:18 IsReadOnly:False, Name:fromShort, Type:System.Int32, Value:582 IsReadOnly:False, Name:private, Type:System.Int32, Value:999 IsReadOnly:False, Name:obj, Type:System.Object, Value: IsReadOnly:True, Name:constFloat, Type:System.Single, Value:2.34 まとめると, こんな感じです. プロパティは取得できない readonlyキーワードやconstキーワードを付けると, ScriptVariable.IsReadOnlyがTrueになる 基本は宣言した順に入る. ただし, staticやconstキーワードを付けたフィールドについては末尾にstatic→constの順で入れられる 4.5 GetVariableメソッド Variableプロパティで取得できるフィールド配列から, 特定のフィールド名を持つものを取得するメソッドです. 取得に失敗した場合はnullが返ります. サンプルコードは省略します. さすがにわかるよね…? 4.6 ContinueWithAsyncメソッド 「実行結果の取得」ではないですが, 同じScriptStateクラスに含まれているので, ここで紹介します. このメソッドは, 現在の状態を保持して指定のスクリプトを実行し, 結果を取得するメソッドです. インタラクティブな実行環境を提供したい場合に最適です. 例えば, Sample_6.csの「フィボナッチ数列を生成する処理」をGlobal Objectを使用せずに書いてみます. Sample_12.cs using System; using Microsoft.CodeAnalysis.CSharp.Scripting; var result = await CSharpScript.RunAsync("int Num_0 = 0; int Num_1 = 1; int Result = Num_0 + Num_1; return Result;"); Console.WriteLine(result.GetVariable("Num_0").Value); Console.WriteLine(result.GetVariable("Num_1").Value); Console.WriteLine(result.ReturnValue); for (int i = 1; i < 10; i++) { result = await result.ContinueWithAsync("(Num_0, Num_1) = (Num_1, Result); Result = Num_0 + Num_1; return Result;"); Console.WriteLine(result.ReturnValue); } このコードを実行すると, 次のようにフィボナッチ数列を得ることができます. output 0 1 1 2 3 5 8 13 21 34 55 89 スクリプト実行機能の各種オプションを弄る (ScriptOptionsクラス) ここまでで, おそらく「スクリプトを読んで, 実行する実装」はできたのではないかと思います. で. ここからは, 自身の目的に最適なオプションを選択するために, ScriptOptionsクラスおよびそこで使用する各種クラスを見ていきます. ScriptOptions.DefaultはCSharpScript.Createメソッド等でoptionsにnullを指定した際に使用されます. ではでは. パブリックメンバを列挙してみます. static ScriptOptions Default { get; } ImmutableArray<MetadataReference> MetadataReferences { get; } MetadataReferenceResolver MetadataResolver { get; } SourceReferenceResolver SourceResolver { get; } ImmutableArray<string> Imports { get; } bool EmitDebugInformation { get; } Encoding FileEncoding { get; } string FilePath { get; } OptimizationLevel OptimizationLevel { get; } bool CheckOverflow { get; } bool AllowUnsafe { get; } int WarningLevel { get; } ScriptOptions WithFilePath(string filePath) ScriptOptions WithReferences(IEnumerable<MetadataReference> references) ScriptOptions WithReferences(params MetadataReference[] references) ScriptOptions AddReferences(IEnumerable<MetadataReference> references) ScriptOptions AddReferences(params MetadataReference[] references) ScriptOptions WithReferences(IEnumerable<Assembly> references) ScriptOptions WithReferences(params Assembly[] references) ScriptOptions AddReferences(IEnumerable<Assembly> references) ScriptOptions AddReferences(params Assembly[] references) ScriptOptions WithReferences(IEnumerable<string> references) ScriptOptions WithReferences(params string[] references) ScriptOptions AddReferences(IEnumerable<string> references) ScriptOptions AddReferences(params string[] references) ScriptOptions WithMetadataResolver(MetadataReferenceResolver resolver) ScriptOptions WithSourceResolver(SourceReferenceResolver resolver) ScriptOptions WithImports(IEnumerable<string> imports) ScriptOptions WithImports(params string[] imports) ScriptOptions AddImports(IEnumerable<string> imports) ScriptOptions AddImports(params string[] imports) ScriptOptions WithEmitDebugInformation(bool emitDebugInformation) ScriptOptions WithFileEncoding(Encoding encoding) ScriptOptions WithOptimizationLevel(OptimizationLevel optimizationLevel) ScriptOptions WithAllowUnsafe(bool allowUnsafe) ScriptOptions WithCheckOverflow(bool checkOverflow) ScriptOptions WithWarningLevel(int warningLevel) recordがほしくなるメソッド群ですね. このほかにScriptOptionsExtensionsクラスにWithLanguageVersionメソッドが実装されているので, そちらについても紹介します. 基本的な使い方は, ScriptOptions.Default.WithAllowUnsafe(true).AddInports("System")みたいな感じで, ScriptOptions.DefaultにWith~~~メソッドやAdd~~~メソッドをつらつら連ねていく感じになります. さすがにメソッドに説明は要らないでしょうから, 各プロパティの意味だけ書いておきます. 1. MetadataReferencesプロパティ スクリプト内でデフォルトで (#rディレクティブなしに) 使用できるアセンブリ群です. デフォルトでは25個のアセンブリがリストされてます. とはいえ, ここで指定されたstringはUnresolvedMetadataReferenceクラスのコンストラクタに渡されて, その渡されたstringが記録されるだけなので, この時点でアセンブリが読み込まれているわけではありません. With~~~メソッドやらAdd~~~メソッドやら, 引数の型の違いで色々ありますが… string(あるいはstring[]系)に関してもUnresolvedMetadataReferenceに変換されるだけで, 実際のロードはスクリプト実行時になります. Assembly(あるいはAssembly[]系)に関しては, いずれもCreateReferenceFromAssemblyメソッドからMetadataReference.CreateFromAssemblyInternalメソッドを利用してMetadataReference型に変換しています. ちなみに, CreateFromAssemblyInternalメソッドを見ればわかるように, Assembly.LocationプロパティがnullもしくはEmptyだと弾かれます. 2. MetadataResolverプロパティ MetadataReferenceResolverクラスは, 簡単に言うと, 参照するDLLやEXEファイルを探すためのクラスです. 通常はデフォルトのままで十分だと思われます. 最終的にMicrosoft.CodeAnalysis.CSharpnamespaceのCSharpCompilationOptionsクラス… というよりかはその基底クラスのMicrosoft.CodeAnalysisnamespaceのCompilationOptionsクラスのMetadataReferenceResolverプロパティにコピーされて使用されるわけですが… どこでforeachで回されることになるReferenceDirectivesが作成されているかつかめず… MetadataReferenceResolverクラスのXMLコメントによると#rディレクティブによって呼ばれるとありますが, ScriptOptionsクラスのMetadataResolverプロパティのXMLコメントには依存関係の解決に失敗したときや参照が未解決なものにも呼ばれるっぽいことが書いてあります(以下の引用文参照). to be used to resolve missing dependencies, unresolved metadata references and #r directives. (DeepL使用 参考訳) 見つからない依存関係、未解決のメタデータ参照、#rディレクティブを解決するために使用されます まぁでも, 使ってみた感じmissing dependenciesのresolveにも使われてそうな感じがします. デフォルトではRuntimeMetadataReferenceResolverが指定されています. [2021年9月11日追記] 完全にScriptMetadataResolverクラスを見落としていました… RuntimeMetadataReferenceResolver は ScriptMetadataResolverを介して触ることになります 詳細は末尾に追記します なお, このクラスはinternalなので, 普通の方法ではインターフェイス経由でしか触れません. MetadataReferenceResolverクラスはabstractなので, デフォルトのものか, あるいは自分で継承して使用することになります. 継承して作成する際の注意事項等は後述します 3. SourceResolverプロパティ SourceReferenceResolverクラスは, #loadディレクティブで指定されたファイルを探すためのクラスです. これは本当に#loadディレクティブによるファイルロード以外には使われてなさげです. デフォルトではSourceReferenceResolverクラスが指定されています. こっちはRuntimeMetadataReferenceResolverとは違ってpublicなので便利です. SourceReferenceResolverのコンストラクタは次のような3種の引数構成をとります. SourceFileResolver(IEnumerable<string> searchPaths, string? baseDirectory) SourceFileResolver(ImmutableArray<string> searchPaths, string? baseDirectory) SourceFileResolver(ImmutableArray<string> searchPaths, string? baseDirectory, ImmutableArray<KeyValuePair<string, string>> pathMap) searchPaths : ここで指定されたディレクトリからファイルが検索されます 但し, baseDirectoryが指定されていた場合は, そこから先に検索されます 相対パスでも構いませんが, CurrentWorkingDirectoryからの相対パスになる点にご注意ください. baseDirectory : ファイルを検索する起点とするディレクトリを指定します 絶対パスを指定する必要があります pathMap : パスの先頭を置換する際のパターンを指定します. NormalizePathメソッドで使われていて, 使用先のPathUtilities.NormalizePathPrefixメソッドを見てみると, 配列をforeachで回して, パスの先頭がKeyに一致したら一致部分をValueに置換して返すような実装になっています. #loadディレクティブで相対パスを指定しても, デフォルトのままだとCurrentWorkingDirectoryからの相対パスとして解釈されてしまうため, baseDirectoryをスクリプトファイルが存在するディレクトリにセットしてあげる必要があります. あるいは, ScriptOptions.FilePathにロード元のスクリプトファイルのパスを指定しても大丈夫です. 4. Importsプロパティ C#10.0でいうglobalなusingディレクティブです. 対象のスクリプトファイルで, 指定したusingが不要になります. 例えば, 次のようなコードを実行できます. Sample_13.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; //通常はSystem.Console.WriteLine(...)にする必要がある _ = await CSharpScript.RunAsync("Console.WriteLine(\"Hello World!\")", ScriptOptions.Default.WithImports("System")); 5. EmitDebugInformationプロパティ デバッグ用の情報を含めるかどうかです. デフォルトではfalseなので, デバッグ用情報(pdb的なの)は含まれません. FilePathプロパティでスクリプトファイルへのパスを指定しないと意味が無いので, ご注意ください. 6. FileEncodingプロパティ スクリプトをデバッグする際に使用します. nullを指定するとエンコーディングが自動で検出されます. 7. FilePathプロパティ スクリプトがファイルから読み込まれている場合に, スクリプトファイルへのパスをセットします. EmitDebugInformationをtrueにしたうえで, 必要に応じてFileEncodingを設定し, このFilePathプロパティにスクリプトファイルへのパスを指定することで, スクリプトのデバッグが可能になります. ちなみに, stringでCreateする場合はFileEncodingの指定が必須になりますので, ご注意ください. stringで渡す際のサンプルを次に示します. Sample_14.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System.Text; const string filePath = @"C:\Users\tetsu\source\repos\TR.Practice.CSharpScript\TR.Practice.CSharpScript\sample.csx"; var scriptFile = System.IO.File.ReadAllText(filePath, Encoding.UTF8); _ = await CSharpScript.RunAsync( scriptFile, ScriptOptions.Default .WithFilePath(filePath) .WithFileEncoding(Encoding.UTF8) .WithEmitDebugInformation(true) ); 8. OptimizationLevelプロパティ スクリプトからILコードに変換する際の最適化レベルです. Debug OR Releaseを選択できます. デフォルトはDebugです. Releaseにすると(当たり前ではありますが)デバッグができなくなる場合があるので, ご注意ください. 9. CheckOverflowプロパティ 整数演算のオーバーフローチェックを行うかどうかです. 以下に例を示します. Sample_15.cs using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System; const string scriptFile = "int A = int.MaxValue; A /= 2; return (A + 1) * 2;"; Console.WriteLine("=== result_1 ==="); var result_1 = await CSharpScript.RunAsync(scriptFile, ScriptOptions.Default.WithCheckOverflow(false)); Console.WriteLine(result_1.ReturnValue); Console.WriteLine("=== result_2 ==="); var result_2 = await CSharpScript.RunAsync(scriptFile, ScriptOptions.Default.WithCheckOverflow(true)); Console.WriteLine(result_2.ReturnValue); intの最大値を2で割って1を足してから2倍にしているのでオーバーフローは確実なんですが, 出力を見ればわかるように, CheckOverflowをfalseにしておくとExceptionがthrowされません. output === result_1 === -2147483648 === result_2 === Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow. at Submission#0.<<Initialize>>d__0.MoveNext() --- End of stack trace from previous location --- at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.Scripting.Script`1.RunSubmissionsAsync(ScriptExecutionState executionState, ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, Func`2 catchExceptionOpt, CancellationToken cancellationToken) at <Program>$.<<Main>$>d__0.MoveNext() in C:\Users\tetsu\source\repos\TR.Practice.CSharpScript\TR.Practice.CSharpScript\Sample.cs:line 16 --- End of stack trace from previous location --- at <Program>$.<Main>(String[] args) 10. AllowUnsafeプロパティ unsafeキーワードの使用を許可するかどうかです. 正確に言えばunsafeコンテキストを許可するかどうかでしょうけど, まぁまぁ. 11. WarningLevelプロパティ 表示する警告(Warning)メッセージのレベルを指定します. レベルの数値についてはC#コンパイラオプションと同じだと思われるので, そちらをご参照ください. XMLコメントには0以上4以下と書いてありますが, コンパイラと同じようにC#9.0以上では5も有効かもしれません. 9999等を入れてもエラーは吐かないので, 将来のために9999等の大きな数字を入れておいてもいいかもしれません. 意図的にWarningを出す#warningディレクティブがスクリプトでは効かなかったので, 実行例は省略します. #errorは効くのに, なんででしょうね? 12. WithLanguageVersionメソッド 拡張メソッドで, 言語バージョンを指定できます. 指定できるLanguageVersionはMicrosoft.CodeAnalysis.CSharp.LanguageVersionに列挙されていますが, Microsoft.CodeAnalysis.CSharpパッケージのVersion 3.11.0ではCSharp10が存在しないので, ご注意ください (まぁまだ正式リリースされてないので当たり前ではありますが) 言語バージョン設定は各プロパティがinternalなので, セット後は外から参照できません. ご注意ください. 独自のMetadataReferenceResolverをつくる (MetadataReferenceResolverクラス) デフォルトのMetadataReferenceResolverであるRuntimeMetadataReferenceResolverはinternalなクラスなので, デフォルトでは指定されていないbaseDirectoryを指定したインスタンスを作って使う…ということは難しいです. [2021年9月11日追記] baseDirectory等はScriptMetadataResolverを介して触ることができました… とはいえ, NuGetサポートはないので, それを求める場合は自力で実装することになります. ということで, 独自のMetadataReferenceResolverを作ってみましょう. MyMetadataReferenceResolver.cs using Microsoft.CodeAnalysis; using System; using System.Collections.Immutable; namespace TR.Practice { public class MyMetadataReferenceResolver : MetadataReferenceResolver { public override bool Equals(object? other) => throw new NotImplementedException(); public override int GetHashCode() => throw new NotImplementedException(); public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties) => throw new NotImplementedException(); } } 初期状態はこんな感じです. RuntimeMetadataReferenceResolverではGACとかからアセンブリを検索するコードが書かれていますが, それを再実装するのは手間なので, ScriptOpions.Default.MetadataResolver経由で資源を再利用しちゃいましょう. MyMetadataReferenceResolver.cs using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Scripting; using System.Collections.Immutable; namespace TR.Practice { public class MyMetadataReferenceResolver : MetadataReferenceResolver { MetadataReferenceResolver Resolver { get; } = ScriptOptions.Default.MetadataResolver; public override bool Equals(object? other) => Resolver.Equals(other); public override int GetHashCode() => Resolver.GetHashCode(); public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties) { var baseResult = Resolver.ResolveReference(reference, baseFilePath, properties); if (!baseResult.IsDefaultOrEmpty) return baseResult; //独自の処理を書く return ImmutableArray<PortableExecutableReference>.Empty; } public override bool ResolveMissingAssemblies => Resolver.ResolveMissingAssemblies; public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) => Resolver.ResolveMissingAssembly(definition, referenceIdentity); public override string ToString() => Resolver.ToString() ?? string.Empty; } } で, ResolveReferenceメソッドに置いた//独自の処理を書くってところに独自の処理を書けばOKです. ちなみに, ResolveReferenceメソッドの引数について… referenceには#rディレクティブで書いた文字列が渡されます baseFilePathにはScriptOptions.FilePathが渡されます で. F# Interactiveでは#r "nuget:Newtonsoft.Json"みたいに書くことでnugetからパッケージを落とせたり, #iディレクティブでパッケージソースを追加できたりできるそうですが, RuntimeMetadataReferenceResolverには実装されていません (というか, CSharpScript自体#iディレクティブをサポートしてなかったりしますが) 一応abstractなNuGetPackageResolverクラスが定義され, RuntimeMetadataReferenceResolverから使用されるような実装になってたりしますが, 現時点でこれを実装したクラスはroslynプロジェクト内になく, またそもそもRuntimeMetadataReferenceResolver自体internalなのでNuGetPackageResolverクラスを継承させたのをセットすることもできません. CSharpScript.CreateメソッドのassemblyLoader CSharpScript.Createメソッドに限っては, InteractiveAssemblyLoader型の引数assemblyLoaderを取るので, そちらでアセンブリのロードを制御することも可能です. InteractiveAssemblyLoader(MetadataShadowCopyProvider shadowCopyProvider = null) InteractiveAssemblyLoaderクラスのコンストラクタはMetadataShadowCopyProviderクラスインスタンスをとります. MetadataShadowCopyProvider(string directory = null, IEnumerable<string> noShadowCopyDirectories = null, CultureInfo documentationCommentsCulture = null) このdirectoryに相対パスの起点とするディレクトリへのパスを指定することで, そこにあるアセンブリの読み込みが可能になります. さいごに 気づいたらすごい長くなりました. たぶん色々間違っているところがあると思うので, 何かありましたらコメント等でご指摘いただけると嬉しいです. 2021年9月11日追記 ScriptMetadataResolverクラスについて コードを読み落としてまして, デフォルトのMetadataReferenceResolverにはScriptMetadataResolverクラスが使用されていました. RuntimeMetadataReferenceResolverはScriptMetadataResolverから触ることになります. ScriptMetadataResolver WithSearchPaths(params string[] searchPaths) ScriptMetadataResolver WithSearchPaths(IEnumerable<string> searchPaths) ScriptMetadataResolver WithSearchPaths(ImmutableArray<string> searchPaths) ScriptMetadataResolver WithBaseDirectory(string? baseDirectory) プロパティを変更する系のメソッドはこれだけです. WithSerchPathsメソッドはRelativePathResolverクラスに渡すパス群を変更するメソッドです. アセンブリを探すディレクトリの完全修飾パス(絶対パス)を指定します WithBaseDirectoryメソッドは, SourceReferenceResolverクラスと同じくアセンブリ検索の起点となるディレクトリです.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む