特集:Visual Studio 2015 Update 1の注目C#機能
csi.exeコマンド登場! C#スクリプト(.csx)やREPLを動かそう
Visual Studio 2015 Update 1で追加されたREPL関連新機能を紹介。コマンドラインで実行できるC#スクリプトやIDEに搭載された[C# Interactive]の使い方も解説する。
Visual Studio 2015 Update 1(以下、Update1)が米国時間で10月30日に公開された。執筆時点ではまだ、Visual Studioの[拡張機能と更新プログラム]には表示されないようだが、次のリンク先(※インストーラーへの直リンク)からダウンロードしてインストールできる。
Update1の新機能の1つとして、C#スクリプト(.csx)とC#のREPL(Read-Eval-Print Loop: 対話型評価環境)がある(※Visual Basic向けは今回リリースされていないが、将来、提供予定だ)。本稿では、この2つの機能内容を紹介する(REPLも便利だが、C#スクリプトが実現できることに、筆者は特に注目しているので、この記事を書いた)。
なお、Update1の全ての新機能&更新機能は次のリンク先で説明されている。
REPLとは? C#のREPL環境についての振り返り
Rubyのirb
コマンドのように、ターミナル(Mac)やコマンドプロンプト(Windows)上でソースコードを入力すると、1行ごとにコードが評価・実行されて、その結果がインタラクティブに出力される実行環境のことをREPLと呼ぶ。このREPL機能が、Visual Studio 2015のIDEに、[C# Interactive]ウィンドウとして新しく追加された。
もちろんこれまでも、サードパーティからいくつかのC#用REPLがリリースされている。代表的なものとして、例えば下記のようなものがある。
- C# REPL | Mono
- scriptcs - Write C# scripts in your favorite text editor
- CShell by ArnovaAssetManagement
今回のUpdate1でマイクロソフトから公式にC#用REPLが提供されたため、わざわざこのようなサードパーティ製をインストールして使うという手間がなくなった。
それでは、具体的にどのような機能が提供されたのかを、1つずつ見ていこう*1。まずはVisual Studio 2015のIDEに搭載された機能から(その次に機能の内部を掘り下げ、後半ではコマンドライン実行やスクリプティングについて説明する)。
- *1 本稿の内容を試した実行環境は、Visual Studio Enterprise 2015をインストールした64bit版のWindows 10 Proである。
Visual Studio 2015の[C# Interactive]ウィンドウ
IDEを立ち上げるとすぐに気付くのが、[C# Interactive]ウィンドウがIDEの下部に表示されるようになったことだ(図1。表示されていない場合は、メニューバーから[表示]-[その他のウィンドウ]-[C# インタラクティブ]を実行すればよい。※「Interactive」の表記が、実際のウィンドウは英語で、メニューは日本語と揺れている……)。
使い方に関しては、特に何も説明する必要はないだろう。>
の後に入力カーソルがあるので、そのままコマンドライン入力のようにコードを入力していき、Enterキーを押せば、その行のコードが実行される。Main
メソッドやクラスなどの階層構造を書く必要がないので、目的のコードをすぐに実行できる。もちろん図2のようにIntelliSenseも利く。LINQやaync
/await
キーワードも問題なく使用できる。
ちなみに、1行で実行せずに複数行記述したい場合は、Shift+Enterキーで改行していき、最後だけEnterキーを押せばよい(図3)。
【一覧】[C# Interactive]ウィンドウのキーボードショートカット
この他にもいくつかのキーボードショートカットが用意されている。数は多くないので、ここで箇条書きで簡単にまとめておこう(※筆者が動作を確認できたもののみを掲載)。
- ↑/↓/←/→キー: 入力カーソルは上/下/左/右に移動させることができる。
- Enterキー: 入力行のコードを評価して実行する。コードの文が完了していない場合、実行されずに改行される。
- Shift+Enterキー: 改行する。評価・実行はしない。
- Ctrl+Enterキー: カーソルのある行を、入力行にコピーする。Ctrlキーを押したままEnterキーを2回連打すると、コピーしたうえで評価・実行する。
- [Escape]キー: 現在行のコード入力をクリアする。
- Ctrl+↑/Ctrl+↓キー: ウィンドウの内容を上/下にスクロールする。
- Alt+↑/Alt+↓キー: コードの実行履歴における前/次の履歴のコードに、入力行を置き換える。同じようなコードを再入力する際に便利。
- Ctrl+Alt+↑/Ctrl+Alt+↓キー: コードの実行履歴の中から、現在途中まで入力している文字列で始まるコードの履歴だけに絞り込み、その中における前/次の履歴のコードに入力行を置き換える。例えば「var」と入力した状態で、このショートカットキーを押すと、「var」で始まるものにフィルタリングした状態で過去の入力履歴から目的のコードを探せる。
- Ctrl+Aキー: 現在の入力行を全選択する。Ctrlキーを押したままAキーを2回連打すると、ウィンドウ内全てを選択する。
その他、クリップボードを使ったコピー&ペーストも可能だ。RTF(リッチテキストフォーマット)であるため、コピーしたコードをWordやPowerPointにペーストすると、構文が色付けされた状態で貼り付けられる。
また、Ctrl+Fキーでウィンドウ内をテキスト検索したりすることもできる。
[C# Interactive]ウィンドウの主な用途
このような機能を提供する[C# Interactive]ウィンドウだが、一体どのような場面で役立つのだろうか。マイクロソフトの公式文書に基づくと、次のような利用ケースを想定しているようだ。
- APIを効率的でインタラクティブに習得する方法として
- 部分的なサンプルコードの実験場として(TIPSやStack Overflowなどのサンプルコードをコピー&ペーストして実行してみるなど)
- 「ある式が何を返すか」や「APIが何をするのか」を検証する際に、すぐにフィードバックが得られる方法として
他には例えば、
- ネットからデータ(例えばJSON)をダウンロードして、インタラクティブにその内容を分析したい場合、もしくは解析コードの書き方を下調べしたい場合
- C#スクリプトを作成するために、インタラクティブにコードを作成・実行・検証したい場合
といった活用法も考えられるだろう。
【コラム】[イミディエイト ウィンドウ]とは何が違うのか?
Visual Studioには以前から、部分的なコードを即時実行できる[イミディエイト ウィンドウ](Immediate window)があるのをご存じだろう。[C# Interactive]ウィンドウは、すぐに1行単位でコードが実行できるという点で、それに似ていると感じられるが、この両者では何が違うのだろうか?
大きな違いは、[イミディエイト ウィンドウ]が基本的にデバッグ用途であり、式レベルの部分的なコードしか記述できないが、[C# Interactive]ウィンドウは、クラスやメソッドを含めたコードも書けるので、実際のプログラミングのようなことが可能である点だ。これにより、後述するC#スクリプトのようなまとまったコードを記述して、バッチ処理のような形で一気に実行することも可能である。既存のC#ソースコードから必要な箇所をコピー&ペーストすれば、(原則的には)それがそのまま動くと考えてよい。
その他、[C# Interactive]ウィンドウにはIntelliSenseやコードの構文ハイライトなど、効率的にコーディングするためのエディター機能が整備されている点が、[イミディエイト ウィンドウ]とは異なる。
このREPL機能でアセンブリへの参照やクラスの使用がどのように実現されているのか、どうやって独自のものを参照したり使用したりできるのか? 次節で少し掘り下げて見ていこう。
REPLによるC#コーディングを可能にしている仕組み
前掲の図3のヘッダー表記部分には、次のように書かれていた。
Microsoft (R) Roslyn C# コンパイラ バージョン 1.1.0.51109
'CSharpInteractive.rsp' からコンテキストを読み込んでいます。
詳細については、「#help」と入力します。
つまりこのREPL機能は、新コンパイラー“Roslyn”によって実現されていることが分かる。
CSharpInteractive.rspファイル
上記の「コンテキスト」とは、アセンブリ(.dllファイル)を参照し、名前空間をusing
しているREPLの内部状態のことである。その状態を整えるためのCSharpInteractive.rsp
ファイル(=C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\PrivateAssemblies
フォルダー内に存在)の内容は、次のようになっている。
/r:System
/r:System.Core
/r:Microsoft.CSharp
/u:System
/u:System.IO
/u:System.Collections.Generic
/u:System.Diagnostics
/u:System.Dynamic
/u:System.Linq
/u:System.Linq.Expressions
/u:System.Text
/u:System.Threading.Tasks
コマンドオプションが並んでおり、/r:
オプションでは「参照する(reference)アセンブリ」が、/u:
オプションでは「使用する(using)名前空間」が指定されている。この内容により、例えばFile
クラスやConsole
クラスがすぐに使えることが分かる。
なお、このCSharpInteractive.rsp
ファイルにないアセンブリを追加するには、
#r "<GACにインストール済みのアセンブリの名前や.dllファイルへのフルパス>"
(例:#r "System.Windows.Forms"
)
というREPLコマンド(=C#言語仕様としては「ディレクティブ」と呼ぶ)を実行すればよい。
ちなみに名前空間を指定したい場合は、通常のC#コードで書く場合と同じで、using
ディレクティブを使ってusing System.Windows.Forms;
のように記載すればよい。図4では、実際にSystem.Windows.FormsアセンブリのMessageBox
クラスを使って、Windowsフォームのメッセージボックスを表示している。
※この画面のとおりに実行すると、メッセージボックスが表示されている間は[C# Interactive]ウィンドウへの入力はブロックされるので注意されたい。
【一覧】[C# Interactive]ウィンドウのREPLコマンド
先ほど#r
というREPL用コマンドを紹介したが、この他にもいくつかのREPLコマンドが存在する。数も少ないので、ここでまとめて箇条書きで紹介しておこう。
- #r: アセンブリを参照する(前述の通り)。
- #help: ヘルプを表示する。
- #reset: コンテキストを初期状態にリセットする。ただし、これまでの実行履歴とウィンドウ内容はそのまま保持される。
- #clsもしくは#clear: [C# Interactive]ウィンドウの画面をクリアする。ただし、実行履歴だけでなく、コンテキストもそのまま保持されるので、例えば再度、アセンブリを参照し直したりする必要はない。
- #load: 外部のC#スクリプト(.csx)ファイルを読み込んで実行する(例:
#load "myCSharpScript.csx"
)。詳しくは後述する。
ちなみに一部の機能は、ウィンドウ上部のボタンから実行することもできる(図5)。左から、[リセット][画面をクリアする][前の履歴][次の履歴]を実行するためのボタンとなっている。
以上、ここまで前半では、[C# Interactive]ウィンドウの内容を説明した。ここから後半では、コマンドライン実行の方法とC#スクリプティングについて解説する。
csiコマンド
C#のREPLは、csi
コマンド*2としても実行できる。その実体であるcsi.exeファイルは、C:\Program Files (x86)\MSBuild\14.0\Bin\amd64
フォルダー(※64bit用*3のcsi
コマンドの場合で、32bit用のcsi
コマンドならC:\Program Files (x86)\MSBuild\14.0\Bin
)内にあるが、通常のコマンドプロンプトではこのフォルダーへのパスが通っていないため、
- 1 通常のコマンドプロンプト上で、
"C:\Program Files (x86)\MSBuild\14.0\Bin\amd64\csi.exe"
というフルパスを入力して実行するか - 2 [スタート]メニューから[すべてのアプリ]-[Visual Studio 2015]-[開発者コマンドプロンプト for VS2015]などを選択して起動したコマンドプロンプト上で、
csi
コマンドを実行
する必要がある。
- *2 「csi」は「CSharp Interactive」を表している。この命名規則に倣って、Visual Basic向けは「vbi」となる予定だ。
- *3 64bit用のフォルダー内にはあるが、執筆時点では32bit用と同じものである可能性が高い。
なお、csi
コマンド用のコンテキストは、同じフォルダー内のcsi.rsp
ファイルで定義されている。このファイルの内容は前述のCSharpInteractive.rspファイルと同じであるため、説明を割愛する。
REPLとしてcsiコマンドを使う場合の注意点
あとは、csi
とコマンドプロンプト上で打てば、REPLとして使うことができる。ただし、先ほどの[C# Interactive]ウィンドウにはあるIntelliSenseが使えないなど、実際のコーディングには不向きと言わざるを得ない状況だ(執筆時点)。そのためcsi
コマンドは、(REPLを起動せずに)後述するC#スクリプト(.csx)ファイルを実行するために使うのが、メインの用途になるだろう(と筆者は考えている)。そこで本稿ではREPLとして使う方法の説明は割愛し、以下ではC#スクリプトを実行する方法を解説する。
C#スクリプトを実行する
C#スクリプトを実行するには例えば、
csi "C:\SampleCSscript\hello.csx"
|
のように、csi
コマンドの後に、単に.csxファイル*4へのパスを指定するだけである。
- *4 拡張子は実は何でもよいが、分かりやすさのため、通常は「.csx」とする。ちなみにVisual Basicスクリプトは「.vbx」となる予定である。
csi
コマンドの使用法を簡単にまとめておこう。
【使用法】csiコマンド
csi [オプション]... [スクリプトファイル.csx] [スクリプト引数]...
[~]
は省略可能を意味し、...
は複数指定可能を意味する。スクリプト引数については後述する。
スクリプトファイル.csx
を指定すると、そのC#スクリプトが実行される。指定しない場合には、REPLが起動する(ちなみに起動したREPLを終了するには、Ctrl+Cキーを押す)。
オプションとしては、以下のものが指定できる(※筆者が動作を確認できたもののみを掲載)。
- /help: コマンド使用法についてのヘルプを表示する(代替可能:
/?
)。 - /i: C#スクリプトを実行した後、REPLモードに移行する。
- /r:<アセンブリ>: 任意のアセンブリへの参照を追加する(代替可能:
/reference
)。カンマ(,
)でつなげて複数のアセンブリを指定することもできる。 - /u:<名前空間>: 使用する名前空間を指定する(代替可能:
/using
、/usings
、/import
、/imports
)。 - @<.rspファイル>: 応答(.rsp)ファイルの内容を、コンテキストに読み込む。.rspファイルには、アセンブリ参照の追加や名前空間の指定をまとめて記述する。
C#スクリプトファイル側で、#r "<アセンブリ>"
ディレクティブやusing <名前空間>
ディレクティブを使うことで、アセンブリ参照の追加や名前空間の指定を記述できるので、/r:<アセンブリ>
オプションや/u:<名前空間>
オプションの出番はあまりないだろう。
また、アセンブリ参照の追加や名前空間の指定を、複数のスクリプトファイルでそれらを共有したい場合には、.rspファイルの利用が便利だ。自作した.rspファイルを、上記の@<.rspファイル>
オプションで指定するだけである。.rspファイルの作り方は、前述のCSharpInteractive.rsp
ファイルの内容をまねればよい。
C#スクリプト(.csx)ファイル
それでは、肝心の.csxファイルの書き方を説明しよう。まずはアセンブリ参照と名前空間の書き方について。
アセンブリ参照と名前空間
csi.rsp
ファイル内で定義されていないアセンブリ参照や名前空間を追加するには、REPLで説明したのと同じで、#r
/using
ディレクティブを使う。
#r "System.Net.Http"
#r "System.Windows.Forms"
#r "C:\SampleCSscript\ClassLibrary1.dll"
using System.Net.Http;
using System.Windows.Forms;
|
#r
ディレクティブは、リスト1に示すようにアセンブリ名を二重引用符("
)で囲む必要がある。自作のクラスライブラリ(例:ClassLibrary1.dll)などのアセンブリも指定できる。
using
ディレクティブは、通常のC#プログラミングと使い方は変わらない。
いずれのディレクティブも、.csxファイルの冒頭に記述する必要がある。
スクリプト変数/スクリプトメソッド/staticスクリプト変数
クラス内などに入れずに、ソースファイル内の階層構造のトップレベルに置いた変数やメソッドは、「スクリプト変数/メソッド」(=トップレベル変数/メソッド)と呼ばれ、スクリプト全体(=グローバル)でアクセスできる。宣言方法も、通常のC#プログラミングと変わらず、リスト2のようになる。
……省略:「#r」によるアセンブリ参照……
……省略:「using」による名前空間の使用……
// スクリプト変数
int number = 10;
var client = new HttpClient();
/* スクリプトメソッド */
HelloMessage();
void HelloMessage()
{
MessageBox.Show("Hello World from a .csx file");
}
// staticなスクリプト変数
static bool staticScriptVariable = false;
|
C#スクリプトでは、既存のC#コードのスニペットがコピー&ペーストされたときに、その変数やメソッドにstatic
修飾子が付与されていても、そのままで動くようになっている。では、スクリプト変数にstatic
修飾子を付けた場合と、通常のスクリプト変数では、何か挙動が異なるのだろうか?
このような変数は「staticスクリプト変数」と呼ばれ、スクリプトロードの最初に処理されるという特徴がある。その影響により、変数の初期化子でawait
キーワードが使えないことになるので注意してほしい。なお、staticスクリプト変数は、一からスクリプトを記述する場合にはほとんど使われないと思われる。
ローカル変数
変数を使いたいが、例えばint i
のようによく使うものをスクリプト内で何度も使いたい場合や、スクリプト内の他の場所から隠ぺいしたい場合もあるだろう。その場合には、トップレベルではなく、階層のスコープ({ …… }
)を作って、その中で使うようにすればよい。
……省略:「#r」によるアセンブリ参照……
……省略:「using」による名前空間の使用……
int i = 0; // グローバルなスクリプト変数
{
int i = 1; // ローカル変数1
Console.WriteLine(i.ToString()); // 1
}
{
int i = 2; // ローカル変数2
Console.WriteLine(i.ToString()); // 2
}
Console.WriteLine(i.ToString()); // 0
|
クラスの利用
ここまでの説明でトップレベルの変数やメソッドがREPLのように使えるのは分かったが、クラスなどはどうやって使えるのだろうか?
答えは「通常のC#プログラミングと変わらない」となる。C#/Visual BasicのInteractive機能の設計では「Copy/Paste Principle of Scripting(CPPoS: スクリプティングでのコピー&ペースト原則)」が提唱されており、既存のコードをほとんどそのまま取り込んで使えるようにする方針だ。ただし、クラスレベルとなり、名前空間(namespace
)は取り込めないので注意してほしい。
例えばリスト4は、コンソールアプリのコードをそのままコピーしたものだ。ただし、Mainメソッドの呼び出しは行ってくれないので、Mainメソッドの呼び出しを手動で記述する必要がある。その際、外部からのメソッド呼び出しになるので、public
修飾子もしくはinternal
修飾子を付与する必要がある。
Program.Main(Args.ToArray());
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Enterキーで終了します");
Console.ReadLine();
}
}
|
スクリプト引数
リスト4に書いたArgs.ToArray()
が気になった人もいるだろう。C#スクリプトは、引数を受け取ることも可能だ。前述の「【使用法】csiコマンド」で示したスクリプト引数がそれで、その内容はArgs
オブジェクトで取得できる。Args
オブジェクトはList<string>
型であるため、リスト4ではToArray()
メソッドを使って文字列配列に変換している。
スクリプト引数を指定してC#スクリプトを実行するためのコマンドラインはリスト5のようになる。
csi "C:\SampleCSscript\hello.csx" arg1 arg2 "a r g 3"
|
.csxファイルパスの後に、引数を指定するだけだ。リスト5の例では3つのスクリプト引数を指定している。なお、引数の文字列内にスペースを含む場合は、二重引用符("
)で囲めばよい。
リスト6は、リスト5により実行されるC#スクリプトファイルhello.csx
の内容である。
var arguments = Args as List<string>;
foreach (var arg in arguments) {
Console.WriteLine(arg);
}
// 出力内容:
// arg1
// arg2
// a r g 3
|
以上の説明で、「C#スクリプトでさまざまな処理が実現できそうだ」というイメージが湧いたのではないだろうか。実践段階に入り、スクリプトのコード量が増えてくると、複数の.csxファイルに分けたり、.NETで自作したアセンブリを参照したりしたいというニーズも出てくるだろう。次に、それらの方法について説明する。
複数の.csxファイルに分離する
C#スクリプトで複数の.csxファイルを使うには、「【一覧】[C# Interactive]ウィンドウのREPLコマンド」で説明した#load
ディレクティブを使用すればよい。
ここでは例として、.csxファイルを4つに分離し、図6のようにmain.csxファイルから、sub1.csx、sub2.csx、sub3.csxという3つの外部C#スクリプトファイルを読み込むこととする。
#load
ディレクティブによる複数の.csxファイルの利用イメージ この場合、例えばsub1.csxファイルがリスト7の内容だとすると、main.csxファイルの冒頭に#load
ディレクティブを記述することで、sub1.csxファイル内のスクリプト変数ex1
を呼び出せるようになる。
var ex1 = "externalCSX1";
|
#load "sub1.csx"
#load "sub2.csx"
#load "sub3.csx"
Console.WriteLine(ex1); // externalCSX1
|
.NETで自作したアセンブリを参照する
C#でクラスライブラリ(.dllファイル)を作成した場合、それをアセンブリとして参照することで、そのアセンブリに含まれる各クラスを.csxファイル内で使えるようになる。アセンブリ参照には、前述のとおり#r
ディレクティブを使う(前述のリスト1を参照)。
例えばリスト9のようなコードで作成したアセンブリClassLibrary1.dll
は、#r "C:\SampleCSscript\ClassLibrary1.dll"
というような記述で参照できる。
using System;
namespace ClassLibrary1
{
public class Class1
{
public void Hello()
{
Console.WriteLine("Hello from dll!");
}
}
}
|
.csxファイルを直接実行しよう!
最後に実用段階に進む人に向けて、ファイルシステム上でダブルクリックすることで、.csxファイルを実行する方法を紹介しておこう。例えばWindowsファイルシステムでは、.batファイルをダブルクリックするとバッチ処理を実行できるし、PowerShellの.ps1ファイルも右クリックメニューから実行できる。これと同じような操作・運用方法を想定している。
1 1つ目として、拡張子.csx
の関連付けを利用した方法を説明する。その設定手順は図7を参考にしてほしい(詳しい説明は割愛する)。
以上の設定で、.csxファイルをダブルクリックするだけで、そのC#スクリプトを実行できるようになる。
2 2つ目として、ショートカットリンクを利用した方法を説明する。これには、実行対象の.csxファイルへのショートカットリンクを作って、そのリンクファイルの右クリックメニューから[プロパティ]を開き(図8)、そこでC#スクリプト実行用のコマンドラインを[リンク先]欄にそのまま全て記入すればよい。この場合、1と比較して、スクリプト引数付きも指定できるというメリットがある。一方で、1と異なり、.csxファイルごとに設定しなければならないというデメリットがある。
■
csi
コマンドはまだ登場したばかりで、具体的な機能仕様は考えられているが、今回のバージョンではまだ使えない機能(例えば#load …… into ……
ディレクティブ)もあった。それでも、現時点でかなりのC#スクリプティングが実現できるのでぜひ試してみてほしい。
C#スクリプトを書くためのエディターとして、当面、[C# Interactive]ウィンドウを使うのが便利だろう。Visual Studio Codeを使いたいところだが、IntelliSense機能などの対応状況の面で、[C# Interactive]ウィンドウの方が使いやすい印象だ。
MacでもC#スクリプトで簡単にちょっとした処理を記述したいと筆者は常々思っているのだが、今回説明したcsi
コマンドは残念ながらMac向けにはまだ提供されていないようなので、今後、リリースされることを期待したい。