ものすごーく長い間、頭を悩ませてきた問題の一つが、先日ようやく判明したので共有しようと思います。
Visual Studio 2013(より前のバージョンも多分)で、比較的中規模~大規模なソリューション(沢山プロジェクトが含まれている)において、クリーンビルド後にも再びビルドが走って「イラっと」する問題です。
そこで、C#のソースコードを解析し、いわゆる「構文木」をオブジェクト表現可能な形でパースする、ニュートラルな独立したライブラリを実装する事にした… というのがRoslynです。当初は「Compiler as a service」 (InfoQ)なワードだけが取りざたされて、何か大きなサービス的なもののように誤解されたこともありましたが、要するにコンパイラのための(低レベルな)インフラをライブラリ化して、様々な環境から共通利用出来るようにしたものです。
別の例として、Officeを挙げます。Officeには「Visual Basic for Application」という、VBチックなスクリプト環境があります。が、VBA、書きたくないです… VBAは徐々にフェードアウトの方向になっていると思いますが、代わりにC#でスクリプトが書けたら、もうちょっとOfficeに前向きになりそうです(Excel向けであれば、商用プラグインが既にあります:FCell)。MicrosoftはOfficeでC#を使えるようにするか? は、まだまだ分かりませんが、Roslynが公開されたことで、その可能性は高くなったと思います。
Roslynのコアは、C#のパーサーライブラリです。そして、極めて依存性が低く設計されています(Portable class library化されています)。が、パースしても構文木が得られるだけで、これが実行可能になるわけではありません。この構文木を食わせて、スクリプトとして実行可能にするインフラが「Roslyn for scripting」です。
流れは以下のようになります。
Roslyn
Roslyn for scripting
スクリプト(文字列)
→ 構文木
→ IL変換(コンパイル)
→ 実行
Roslyn for scriptingは、構文木をコンパイルしてIL命令に変換し、更に実行まで行います。IL変換は、System.Reflection.Emitを使用して動的にILを生成します。Emitは環境によって使用出来ない(例:ストアアプリ環境であるWinRTではEmitは使えない)ため、Roslyn本体には含めなかったのだと推察しています。現在のところ、Roslyn for scriptingは.NET4.5以上の環境でのみ使用可能です。
話が複雑になってきましたか? いやいや、Roslyn for scriptingは、ものすごく簡単に使えます!
Hello! C# scripting!!
Roslyn for scriptingを使ってスクリプト環境を作る場合、殆どRoslynを意識する必要はありません。とりあえずHello worldをやってみましょう。.NET 4.5以上のコンソールアプリケーションのプロジェクトを作ってください。次に、NuGetで以下のパッケージを導入します。まだ正式版ではないので、「リリース前のパッケージを含める」を選択して下さい。
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.CSharp;
namespace HelloRoslynForScripting
{
class Program
{
static void Main(string[] args)
{
// using System.Collection.Generic; 相当
var options = new ScriptOptions().
WithNamespaces(
"System.Collections.Generic").
WithReferences(
typeof(object).Assembly);
// var list = new List<string> { "ABC", "DEF" }; System.Console.WriteLine(string.Join(",", list));
var script1 = CSharpScript.Create(
"var list = new List<string> { \"ABC\", \"DEF\" }; System.Console.WriteLine(string.Join(\",\", list));",
options);
script1.Run();
}
}
}
/// <summary>
/// Creates a new <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptOptions"/> with the namespaces changed.
/// </summary>
public ScriptOptions WithNamespaces(IEnumerable<string> namespaces)
{
// (内部実装)
}
using Microsoft.CodeAnalysis.Scripting.CSharp;
namespace HelloRoslynForScripting
{
class Program
{
static void Main(string[] args)
{
var script1 = CSharpScript.Create(
"Console.WriteLine(\"Hello C# scripting for first line, and continue...\");");
script1.Run();
var script2 = CSharpScript.Create(
"Console.WriteLine(\"Hello C# scripting for next line!\");");
script2.Run();
}
}
}
using Microsoft.CodeAnalysis.Scripting.CSharp;
namespace HelloRoslynForScripting
{
class Program
{
static void Main(string[] args)
{
var script1 = CSharpScript.Create(
"var data = 123;");
script1.Run();
var script2 = CSharpScript.Create(
"Console.WriteLine(\"Hello C# scripting with data: {0}\", data);");
// "(1, 56): error CS0103: The name 'data' does not exist in the current context"
script2.Run();
}
}
}
using Microsoft.CodeAnalysis.Scripting.CSharp;
namespace HelloRoslynForScripting
{
class Program
{
static void Main(string[] args)
{
var script1 = CSharpScript.Create(
"var data = 123;");
var script1State = script1.Run();
var script2 = CSharpScript.Create(
"Console.WriteLine(\"Hello C# scripting with data: {0}\", data);");
script2.Run(script1State);
}
}
}
/// <summary>
/// Runs this script.
/// </summary>
/// <param name="globals">An object instance whose members can be accessed by the script as global variables, or a <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptState"/> instance that was the output from a previously run script.</param>
/// <returns>
/// A <see cref="T:Microsoft.CodeAnalysis.Scripting.ScriptState"/> that represents the state after running the script, including all declared variables and return value.
/// </returns>
public ScriptState Run(object globals = null)
{
// (内部実装)
}
using System;
using System.Linq;
using Microsoft.CodeAnalysis.Scripting.CSharp;
namespace HelloRoslynForScripting
{
public sealed class HostObject
{
public int Value;
public string CreateValues(int count)
{
var r = new Random();
return string.Join(",", Enumerable.Range(0, count).Select(index => r.Next()));
}
}
class Program
{
static void Main(string[] args)
{
var hostObject = new HostObject { Value = 123 };
var script1 = CSharpScript.Create(
"Console.WriteLine(\"Hello C# scripting with host object: Value={0}, Values=[{1}]\", Value, CreateValues(10));");
script1.Run(hostObject);
}
}
}
UNetbootin allows you to create bootable Live USB drives for Ubuntu, Fedora, and other Linux distributions without burning a CD. It runs on Windows, Linux, and Mac OS X.