rider.お品書きサクッと書いて公開して、後でちまちま修正する事がママあります。なんじゃこりゃ?と思ったら、数日してから見て貰えると嬉しいです。「スクラムの実践方法を分かりやすくマンガも交えて解説。Scriptingで、あなたのアプリケーションにもC#スクリプトを!!」の改訂版なので、先にこちらを眺めてもらえるとスムーズに理解できると思います。Scriptingの正式版が公開されました!前記事ではまだ正式版ではありませんでしたが、とうとう正式版が公開されました。この記事では、以前のバージョンと正式版との違いについて焦点を当てたいと思います。2015リリースに向けての追い込みの時期に公開されたものです。2015に合わせて正式リリースするつもりであるように見えました。バックログを見ても色々課題が残っており、本当にあと少しで間に合うのかという不安もあったのです。rc3でそれらのコードはごっそり削除、NuGetの更新も途絶えてしまったのです。私が知っている(issueにも挙げた)課題としては、以下のようなものがありました。外部から与えるグローバルインスタンスがDynamicObjectの場合でも、スクリプト環境はそれを認識できない。メソッドの即時実行を行った場合に、非同期メソッドであった場合は、グローバルなクロージャが非同期コンテキストを認識できない。スクリプティング環境では、Roslyn自体は内包されていてアクセス出来ない。グロサミで私の写真がちらっと出た時はびっくりしました。嬉しかったこれらがどのように解決されたのかも検証したいと思います。正式版のバージョンは1.1.1です。Update1とともにリリースされました。RoslynやScriptingのNuGetパッケージは独立しているので、VSのバージョンと関係なく個別に使用することができます。4.6以上が必要です。NuGetのパッケージのターゲットフレームワークが「dotnet」となっており、いわゆる「netcore5」に対応するのですが、従来の環境では4.6以上が必要になります。実現内容を考えると、もっと古いターゲットでも動作させられる可能性はあると思うのですが、netcore5から導入されたライブラリのNuGet細分化を前提としているため、移植は別の意味で面倒かもしれません。パッケージ名は「Microsoft.CodeAnalysis.CSharp.Scripting」です。NuGetでサクッとインストールしましょう。Microsoft.CodeAnalysis.CSharp.Scriptingさて、正式版で変更を受けたポイントは2つあります。時間のかかる処理は、すべて非同期(Task)ベースとなりました。デフォルトオプションは完全に「空」になりました。時間のかかる処理とは、Scriptingで言うならRunメソッドです。rc2ではRunメソッドを呼び出すと、スクリプトが実行されて結果が返却されました。この部分がTaskベースの非同期処理となり、名前も「RunAsync」メソッドとなりました。後方互換性は維持されていないため、正式版を使用する場合は修正が必要です。言っても、難しいことは無いでしょう。script");}上の例のように、RunAsyncメソッドをawaitで待機します。戻り値のTaskを使ってゴニョゴニョすればOKです。合理的な変更だと思います。Createメソッドはジェネリックバージョンもあり、RunAsyncの戻り値を型指定で伝搬させ、スクリプトの式評価の結果をタイプセーフに得る事もできます。この辺りも「こうなればいいのに」と思っていたことが実現されていて好感が持てます。実は上のコードは動作しません。RunAsync呼び出し時に例外が発生します。例外の内容を見ると、「Console」が見つからないと出力されています。rc2では、デフォルトのインポート節(C#で言うところのusing句)として「System」が含まれていたのですが、正式版ではこれが削除されて、何もインポートされていない状態となっています。そのため、自分でインポートを追加する必要があります(または、スクリプトコードにusing句を追加するか、完全限定名でクラスを指定します)。修正したコードは以下の通りです。script");}WithOptionsメソッドで、スクリプトにオプションを指定できます。前回の記事ではインポート(名前空間)と参照を加えていましたが、ここの扱いは今回も変わっていません。明示的に”System”を追加してあります。参照も追加してありますが、mscorlibの範疇の型は追加しなくても認識出来るようです。グローバルコードの扱いグローバルコードの解釈が練られ直されたようです。メソッド呼び出しConsole.WriteLine("ABC");のように、名前空間の指定もクラスや構造体もメソッドの定義もない所からの実行可能な式や文を許容します(許容する仕様です)。これが満たされないと、スクリプティングの逐次実行(要するにインタプリタのように使う)が出来ないので、魅力半減です。このような文は、内部でコード生成された時点で、不透明な外郭クロージャクラスとクロージャメソッドが作られ、その中で文を実行する事になります。所で、次のようなスクリプトを考えてみます。httpClient.GetStringAsync("http://www.bing.com/");}このコードの戻り値はTask<string>となります。コード中でawaitを使っているからなのですが、これも通常のコードとしてコンパイルする場合は、メソッドシグネチャに「async」と書かないとコンパイルエラーになります。同じRoslynを使ってパースしているのですが、グローバルコードではasyncを書かなくても(書けないが)、awaitを認識して、Taskクラスのインスタンスとして返却するのです。この仕様はかなり奇妙ですが、コードを書かれるまでは、それが同期的なのか非同期的なのかが分からないので、仕方ないと割り切った感があります(これを許すなら、互換性のためのasync句とは何だったのかという気もしなくもない)。クロージャーとなる実装がどのようなものかを、簡単に眺めてみます。MoveNext興味深い事です。クロージャーの実装は、”Submission#0′′クラスのインナークラス”<>d__0′′に作られた”MoveNext”メソッドのようです。これは、C#コンパイラがAwaitableなステートマシンを作るときの形に似ているので、おそらくそのまま再現されているのでしょう。仮にスクリプトブロック内にawaitを使わなかったとしても、初めからasync-awaitが有効なコードブロックとして評価している事が分かります。問題はそれだけではありません。「遅延実行」が絡む場合は、いつ実際に評価を行うのかが難しいのです。httpClient.GetStringAsync("http://www.google.com/");なまじ、async-awaitがクロージャーに内包されて実行されている事を知っていると、このコード片がどのように実行されるのかに迷いが生じます。最初と2回目の継続処理は、単一のクロージャーにまとめられる必要があるのではないかと。「スクリプトである」と考えれば明確で、逐次処理単位毎に実行されます(つまり書いた通りの感じで逐次実行される)。逐次処理の検証をしてみます。ScriptStateのAPIも整理されました。script");}これで、最初のRunAsyncでBingが、次のContinueWithAsyncでGoogleが参照されます。「return」句を入れると、スクリプトとしてのコードの性質がはっきりします。script");}結局、逐次処理の単位毎に、式の結果として評価された値が返される事が分かります。同時に、逐次処理させても、内部のクロージャーは包含された大きなメソッドを作るわけではなく分割され、しかし定義された変数は引き続き使えるようにしているのです。このような挙動を見ていると、同じC#でRoslynでありながら、直観的なコードはかなり感じが違うなと思いました。メソッドやクラスを定義する事は可能なので、そこまでやればコンパイル前提のC#のコードと変わりませんが...DynamicObjectの扱いホスト環境との通信として、グローバルにアクセス可能なインスタンスを用意して提供すると、スクリプトからアクセス出来る、という機能がありました。script");}RunメソッドからRunAsyncメソッドに変わりましたが、引数にホストオブジェクトを渡すことが可能な点は変わっていません。ホストオブジェクトの型を明示的に指定する必要があります。C#スクリプトから見て、「静的」には何の型に見えるのかを明示する必要があるのでしょう。CSharpScript.Createに引数で指定します(例ではオプションの指定も引数に寄せてあります)。Microsoft.CSharp.RuntimeBinder.Binderクラスのアセンブリを参照していますが、これは”Microsoft.CSharp”アセンブリを使うためで、要するにDLR対応です(dynamicキーワードを使う場合に必要)。rc2では上記のコードを書いた場合、IdとNameプロパティには「アクセス出来ません」でした。issueに上げたのですが、どうやら今回も対応しなかったようです。これが出来ると、ホスト側のアクセス可能な要素を動的に定義できると思ったんですけどね。Scriptingは、当然内部でRoslynを使用しているので、スクリプトのパース結果はRoslynの抽象構文木で表現されているはずです。rc2では一体それがどこからアクセス可能なのか良くわかりませんでしたが、1.1.1ではScriptクラスのGetCompilationメソッドからアクセス出来ます。script");}ToStringして表示しているだけなので何の捻りもありませんが、パース結果をロジック的に探索出来ます。これを使ってIntelliSenseのような表示の仕掛けも可能でしょう。各種リゾルバースクリプトのコンソールインターフェイスが存在する場合は、ソースコードの参照先パスや、アセンブリの参照先パスを指定して、動的に読み込みをサポートしたくなるかもしれません。これらは通常はScriptOptions.WithFilePathやWithReferencesを使用して指定しますが、スクリプト環境内からの参照解決要求に応答するように実装する事も出来ます。ScriptOptions.SourceResolverやMetadataResolverがそれに該当し、リゾルバー基底クラスを継承して、独自の参照解決を実装して指定する事が出来ます。Scriptingでは、「REPL」と呼ばれる帯域外コマンドが正式に決定されました。以下のように「#r」コマンドを使うと、スクリプト内で環境にアセンブリをロードすることが出来ます。script");}この時、ScriptOptions.MetadataResolverには、RuntimeMetadataReferenceResolverがデフォルトで設定されており、このリゾルバーが標準的な.NETのアセンブリロード処理を行う(この場合は、System.Net.Http.dllをGACから読み取る)ので、#rコマンドが成立するのです。Scriptingの足掛かりは出来たかなと思います。Roslyn、特にASTの辺りはスクリプティングとは関係なく、これまた膨大なトピックが詰まっていると思います。2015の「アナライザー」が、Roslynへの入り口となると思います。OSS化され、引き続き改良が進むと思われるため、当分の間はこのインフラが使用される事でしょう。参考になれば幸いです。WordPress%d人のブロガーが「いいね」をつけました。