Bakulog

獏の夢日記的な何か。

【C#】Roslyn for Scripting試してみた【スクリプト】

「動いたぞー!」という程度ですが一応。個人的に使いたい機能のメモを兼ねてます。

 

2015/11/16追記: NuGetパッケージの置かれ方がちょっと変わったので全体的に書き直しました。

2015/12/5追記: 最新版が安定版(1.1.1)となっていたので少し書き換えました。

 

Roslyn for ScriptingはRoslynコンパイラの機能の一環であり、実行時にスクリプトとしてC#とかのソースコードコンパイルして走らせられるパッケージです。Roslyn for Scripting自体の基本的な導入については「Roslyn for Scriptingで、あなたのアプリケーションにもC#スクリプトを!」の記事や同記事に載っている参考先をいろいろ見れば分かるのですが、実際にやろうとしたら細かいところでつっかかったので具体的な手順メインでRoslyn for Scriptingの導入についてメモしておきます。

 

本記事を書いてる段階(2015/11/16時点)ではRoslyn for Scriptingはプレリリース版なので正式リリース版と仕様が違う可能性があることにご注意ください。

 

もくじ

  1. NuGetからパッケージを拾ってくる
  2. とりあえずHello World
  3. 単体のスクリプトファイルを実行
  4. スクリプトからスクリプトをロードして使う
  5. スクリプトのロードってフルパス書かないとダメなの?

 

NuGetからパッケージを拾ってくる

普通にプロジェクト(今回はコンソールアプリケーション)を作成してプロジェクトのターゲットフレームワーク.NET Framework 4.6にします(メニューバー「プロジェクト→(プロジェクト名)のプロパティ」を開き、「アプリケーション」タブで「対象のフレームワーク」を設定)。で、パッケージマネージャコンソール(「ツール→NuGetパッケージマネージャ→パッケージマネージャコンソール」)を開いて下記のコマンドを入力したらインストール完了です。

PM> Install-Package Microsoft.CodeAnalysis.CSharp.Scripting

ソリューションエクスプローラの参照から「NuGetパッケージの管理…」でGUIウィンドウを開いて上記のパッケージ名"Microsoft.CodeAnalysis.CSharp.Scripting"で検索、インストールするのでも変わりません。ちなみに私はCUIが怖いのでGUIウィンドウの方をよく使います。

※2015/12/5: 以前に記事を書いた時点ではインストールコマンドで"-Pre"オプションが必要でしたが現在は安定版がリリースされたため不要になりました。

 

とりあえずHello World

本節の例は冒頭にも載せた記事「Roslyn for Scriptingで、あなたのアプリケーションにもC#スクリプトを!」に載ってるHello Worldサンプルとほぼ同じです。Program.csをこのように書いて実行します。

using Microsoft.CodeAnalysis.CSharp.Scripting;

namespace HelloRoslyn
{
    class Program
    {
        static void Main(string[] args)
        {
            CSharpScript.RunAsync("System.Console.WriteLine(\"Hello, World!\");")
                .Wait();
        }
    }
}

うまく行けばコンソールに"Hello, World!"が表示されます。紹介元の記事が書かれた頃との仕様差はこんな感じでしょうか。

  • 名前空間が"Microsoft.CodeAnalysis.Scriptping.CSharp"から"Microsoft.CodeAnalysis.CSharp.Scripting"になった
  • スクリプトの実行メソッドはAsyncなものだけになった(ので、async/awaitできないMain関数内ではWaitしないと完了待ちできない)
  • スクリプト内では何も書かなくてもusing System; が実行済み」という仕様が無くなってる(ので、冒頭にusing文を書くか名前空間をきちんと指定しないとConsoleクラスとかが呼べない)

 

 

単体のスクリプトファイルを実行

こちらは特に難しい話ではなくて、単に中身を全部読み込んでソースコードとして突っ込めばいいだけです。プログラムの実行ディレクトリにテキストファイルとして"test.csx"を配置し、中身を次のようします。

using System;

Console.WriteLine("Hello, World!");

いっぽうMain関数はこう書き直します。

using Microsoft.CodeAnalysis.CSharp.Scripting;
using System.IO;

namespace HelloRoslyn 
{
    class Program 
    {
        static void Main(string[] args) 
        {
            CSharpScript.RunAsync(File.ReadAllText("test.csx"))
                .Wait();
        }
    }
}

さっきと同じ出力が得られます。

 

スクリプトからスクリプトをロードする

スクリプト上で他のスクリプトをロードするにはロード用のディレクティブ"#load"を使います。今度はプログラムの実行ディレクトリに二つのスクリプト"test.csx"と"test2.csx"を用意します。

//test.csx
#load "C:\Users\(ユーザ名)\Documents\Visual Studio 2015\Projects\(プロジェクト名)\(プロジェクト名)\bin\Debug\test2.csx"

var h = new Hoge();
h.SayHello();

 

//test2.csx
using System;

public class Hoge
{
    public void SayHello() 
    {
        Console.WriteLine("Hello World!");
    }
}

こんな感じでうまく行きます。"#load"で指定してるスクリプトのパスは絶対パスで正しく指定してればデスクトップディレクトリのスクリプトを拾う事なども可能です。

 

スクリプトのロードってフルパス書かないとダメなの?

スクリプト絶対パス指定でロードするのは何かイヤなので、ScriptSourceResolverクラスを使ってMain関数をこんな感じに変更します。

using System;
using System.IO;

using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting;

namespace HelloRoslyn
{
    class Program
    {
        static void Main(string[] args)
        {
            var ssr = ScriptSourceResolver.Default.WithBaseDirectory(
                        Environment.CurrentDirectory
                        );
            var so = ScriptOptions.Default.WithSourceResolver(ssr);

            CSharpScript.RunAsync(File.ReadAllText("test1.csx"), so)
                .Wait();
        }
    }
}

こうすると実行プログラムのディレクトリ基準でスクリプトを探すようになり、相対パス表記でもスクリプトがロードできます。

#load "test2.csx"

var h = new Hoge();
h.SayHello();

なおスクリプトの探索先をより複雑に指定したい場合BaseDirectoryを変更する代わりにWithSearchPathsというのを使うとうまく行きます。

 

実際はこれだけではなく既定の参照アセンブリを追加したりグローバル変数の追加をしたりと色々な設定が可能ですが、そのへんは触ってれば分かると思うので今回はこの辺でおしまいにします。

 

本記事の続きとしては「Roslyn for Scriptingでデスクトップマスコットのコードを改善しました」系のネタをそのうち投下する予定なので続報にご期待下さい。