はじめに
RoslynはC#の構文解析やコード評価を行えるライブラリでC# 6以降のコンパイラでも使われています。Roslynを利用すると構文解析された結果を利用できるので文字列比較や正規表現と比べると解析漏れをなくせます。
RoslynのインストールはNuGetで使えますがUnityだと重複するdllがありそのままでは導入できません。それをUPMで簡単に導入する方法が分かったのでまとめてみました。
- UPM化されたRoslynのドキュメント Code Analysis
Unityでは以下のプロダクトでRoslynが使われています。
- UPMのImmediate Window
- Utf8JsonのUtf8Json.UniversalCodeGenerator
- MessagePack-CSharpのMessagePack.Generator
環境構築
Unity2018.3以上
- Package Managerから
Add package from git URL...
できるバージョンではcom.unity.code-analysis
を追加する - Package Managerから
Add package from git URL...
できないバージョンではPackages/manifest.json
に"com.unity.code-analysis": "0.1.2-preview",
を追加する
インストールできると以下のようにPackage Managerに追加されます。
インストールだけではDLLを参照できないのでRoslynを使いたいスクリプトフォルダにアセンブリ定義を作り、以下の設定を変更します。
- 一般 > リファレンスをオーバーライドをチェック
- アセンブリ参照に
Microsoft.CodeAnalysis
で始まるdllを追加 - エディタ拡張で使うならプラットフォームの
Editor
のみをチェック、アプリ内で使うならデフォルトの任意のプラットフォーム
のままでOK
Roslynのサンプル
Scripting API SamplesとGetting Started C# Syntax Analysisを参考にサンプルを実行してみます。それぞれcode欄に実行または解析するコード、result欄にその結果を表示しています。また実行できるUnityプロジェクトは https://github.com/shiena/UnityRoslynSample にありメニューの Tools > Roslyn Sample
を選択するとウインドウが開きます。
Evaluate a C# expression
コードを実行します。
string code = "1 + 2";
var result = CSharpScript.EvaluateAsync(code);
Evaluate a C# expression (strongly-typed)
ジェネリクスで結果の型を指定してコードを実行します。
string code = "1 + 2";
var result = CSharpScript.EvaluateAsync<int>(code);
Parameterize a script
クラス定義したパラメータをコードに適用して実行します。
public class Globals
{
public int X;
public int Y;
}
string code = "X+Y";
var globals = new Globals {X = 1, Y = 2};
var result = CSharpScript.EvaluateAsync<int>(code, globals: globals);
Query Methods
コードを解析してMainメソッドの最初の引数を出力します。
string code =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax) tree.GetRoot();
var firstMember = root.Members[0];
var helloWorldDeclaration = (NamespaceDeclarationSyntax) firstMember;
var programDeclaration = (ClassDeclarationSyntax) helloWorldDeclaration.Members[0];
var mainDeclaration = (MethodDeclarationSyntax) programDeclaration.Members[0];
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
var firstParameters = from methodDeclaration in root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
where methodDeclaration.Identifier.ValueText == "Main"
select methodDeclaration.ParameterList.Parameters.First();
var argsParameter2 = firstParameters.Single();
SyntaxWalkers
コードを解析してSystemまたはSystem.以外で始まるusingを出力します。
class UsingCollector : CSharpSyntaxWalker
{
public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>();
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
if (node.Name.ToString() != "System" &&
!node.Name.ToString().StartsWith("System."))
{
this.Usings.Add(node);
}
}
}
string code =
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo { }
}
namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;
class Bar { }
}
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax) tree.GetRoot();
var collector = new UsingCollector();
collector.Visit(root);
参考リンク
- UPMのImmediate Window
- Utf8JsonのUtf8Json.UniversalCodeGenerator
- MessagePack-CSharpのMessagePack.Generator
- UPM化されたRoslynのドキュメント Code Analysis
- Unity Roslyn Sample