読者です 読者をやめる 読者になる 読者になる

【C#】今更ScriptCsを触ってみる

前書き

最近新しくお仕事用のPCを貰ったはいいんですが、こう、端的に言って、とんでもないクソスペックです。

Windows 7は別にいいんですが、32bitだし、メモリ1GBしかないし…。今どき1GBのメモリなんてExcel開いてるだけで枯渇しますよ。

まぁ開発用ではないので何だかんだで使えはするんですが、こんなクソスペックでちょっとIDEでも入れるかーなんてとても思えません。

でもちょっとしたI/O操作とかスクリプト組みたいよねーって気持ちは常にあるので、適当なスクリプト言語の環境を整えることにしました。

でもI/O操作を考えるとC#が一番楽なんだけどなぁ…Visual Studioをこいつで起動するのはなぁ…と悩んでいたところ、ScriptCsの存在を思い出したので、導入することにしました。

ScriptCsってなに?

Roslynちゃんの力をフルに使ってC#をそのままスクリプト言語のように扱えるようにしてしまった謎の実行環境です。

InfoQの記事がとてもわかりやすいので読んでおきましょう。

インストール方法

Download Zip Fileとか書いてあるんでそこを押したくなるんですが、これは罠です。GitHubのmasterブランチがドカンと落ちてきます。

インストールするにはChocolatey経由で落としてこいとのことです。雑ですね。

もう既にChocolateyをインストールしている人はこれで落としてきましょう。Chocolatey自体のインストール方法はこっそりと以前の記事に書いてあります。

ちなみに、Roslynを動かす関係上.NET Framework 4.5以上が必須です。

注意点

微妙にハマったところだけ先に紹介しておきます。

  • ScriptCSのカレントディレクトリはscriptcs.exeが置いてあるディレクトリではなく、scriptcs.exeを呼び出したディレクトリとなる。
    • I/O操作は勿論、#load(後述)で呼び出すスクリプトのパスにも関わってくるので注意が必要。
  • dynamicは使用できない。
    • そもそもRoslynが対応してないっぽい。
    • そのせいでDynamicObjectも使えない。折角のスクリプト言語っぽい言語仕様なのに…!

REPL

インストールし終わったらscriptcs.exeを引数なしで叩いてみましょう。REPLが起動するはずです。

C:\> scriptcs
scriptcs (ctrl-c or blank to exit)

> var message = "Hello, world!";
> Console.WriteLine(message);
Hello, world!
> 

usingもnamespaceもclassもMainメソッドも必要ありません。これだけで動きます。REPLでなく、ファイルに書きだしたものを読み込ませても当然動きます。Roslyn様々ですね。

スクリプトファイルの読み込み

引数にファイル名を渡してあげれば勝手に実行してくれます。拡張子はcsxが推奨されています。別にcsxじゃなきゃ読み込んでくれないわけでもないです。

ただまぁ、文字コードはBOMつきUTF-8にしておいてあげましょう。Roslynちゃんは渡された文字列を問答無用でBOMつきUTF-8としてパースします。

試しに先ほどのサンプルをtest.csxとして保存し、「scriptcs test.csx」と実行すればコンソール上に「Hello, world!」が表示されるはずです。

スクリプト内でのファイル読み込み

とりあえずこんなコードを用意しました。ファイル名は「Hoge.csx」です。

public class Hoge {
    public string Fuga {get; set;}
    public int Piyo {get; set;}
    
    public string Foo() {
        return "Foo";
    }
}

#loadを使うことでスクリプト内でこれを読み込むことが出来ます。ちなみにREPLだと簡単に変数の中身が確認できるので、printfデバッグがしやすいです…と言うか、それ以外のデバッグ方法がないです。

C:\> scriptcs
scriptcs (ctrl-c or blank to exit)

> #load "Hoge.csx";
> var hoge = new Hoge { Fuga = "fuga", Piyo = 1 };
> hoge
{
  "$id": "1",
  "Fuga": "fuga",
  "Piyo": 1
}
> hoge.Foo();
"Foo"
>

スクリプト内でのアセンブリの読み込み

#loadと同じ感覚で#rを使うことでGACに登録されているdllを読み込む事も出来ます。何か参照設定が足りてないな?と思ったら使いましょう。

これは例を書いても仕方ないので、GitHubのサンプルを読んでおいてください。

また、これを使うことで既存の資産であってもバリバリ呼び出すことが出来ます。GACに登録するのはちょっと面倒だけども。

[2015/02/22追記]別にGACに登録されている必要はありません。相対パスを指定してあげればdllを読み込んでくれます。

ちなみに以下のアセンブリは自動で読み込まれています。

  • System
  • System.Core
  • System.Data
  • System.Data.DataSetExtensions
  • System.Xml
  • System.Xml.Linq

また、以下のnamespaceは暗黙的にusingが指定されています。

  • System
  • System.Collections.Generic
  • System.Linq
  • System.Text
  • System.Threading.Tasks

「普通にコンソールアプリケーションを作る時の設定と一緒」と覚えておけば十分でしょう。

Nuget

ScriptCSはなんとNugetまで内蔵しています。ここにあるパッケージは全て流用可能です。

インストール方法も極めて単純です。例えばJson.NETであれば、Nugetのコマンドはこうなっているわけですが、

PM> Install-Package Newtonsoft.Json

そのまま以下の形に置き換えるだけです。

scriptcs -install Newtonsoft.Json

当然依存関係があればそれも一緒に引っ張ってきてくれます。

インストールしたパッケージは「scriptcs_packages」と言うディレクトリで管理されます。ここに保存されたものは#rも#loadも必要ありません。使いたい時にusingでnamespaceを指定すればOKです。

が、一つだけ注意しなくてはならないことがあります。scriptcs_packagesもscriptcs.exeを呼び出したディレクトリ(カレントディレクトリ)に作成/保存されますし、スクリプト内でもカレントディレクトリにscriptcs_packagesがなければそのパッケージを読み込みません。

Script Packs

ScriptCSで使用されることを目的としたパッケージ群であるScript Packsなんてものもあります。ほとんどがNugetで配布されているのでやはり簡単にインストールできます。

これらはScriptCs.Contractsを継承しており、Require<TScriptPack>()と言う形式で呼び出すことが出来ます。例を書くのが面倒なのでGitHubの例を読んでおいてください。

[2015/02/22追記]

起動時引数の設定方法

大事なことを書き忘れていました。ScriptCSでも起動時の引数を設定することができます。

先に引数の取得から説明しましょう。Env.ScriptArgsReadOnlyCollection<string>として入っています。ReadOnlyCollectionはIEnumerableを実装していないので注意しましょう。

サンプルコードとしてはこんなものでいいでしょう。test.csxとでもしておきます。

Console.WriteLine(Env.ScriptArgs[0]);

引数を渡すのはこんな感じです。

C:\>scriptcs test.csx -- test

簡単ですね。マルチバイト文字やスペースを含むような引数の場合はダブルクオーテーションでくくればOKです。

C:\>scriptcs test.csx -- "ほげほげ" "ふが ふが"

ちなみにREPLで起動する場合は引数を渡せません。なんでじゃ。

まとめ

と言うわけで、既存の資産のバリバリ使用しつつちょっとしたコードをガンガン書けるので中々に便利です。

「好きなエディタでC#を書けるよ!」を売り文句の一つにしていますが、ぶっちゃけそれは非常に辛いので実際にはCShellあたりで書くことになると思います。

C#が大好きすぎてスクリプト言語全般に死んで欲しい人は是非試してみてください。