妄想プログラマのらくがき帳 : 2014

2014年11月24日月曜日

[Roslyn]SyntaxRewriterでソースコードを変更してみる。

今回は SyntaxRewriter を用いた方法でソースコードを変更してみます。

SyntaxRewriter を使ってアクセス修飾子を付加する

C#はメンバ変数のアクセス修飾子を指定しないと既定で private になります。そのため private 変数の場合、private を付けても付けなくても良いのですが、コード内で private 有りと private 無しが混在してると統一感に欠けるので統一したいところです。が、既存コードがすでに有り無し混在状態だと手作業で修正するのは大変ですよね。そこで SyntaxRewriter の出番です。

と前置きはこれくらいにしておいて、サンプルコードは以下の通りになります。


まず CSharpSyntaxRewriter を継承したクラスを作成し、 Visit***() メソッドをオーバーライドします。今回はフィールド宣言が変更対象なので、VisitFieldDeclaration() をオーバーライドします。
VisitFieldDeclaration() は SyntaxTree 内の各 FieldDeclarationSyntax ごとに呼び出されるメソッドで、メソッド内で引数で渡された node を変更し、変更後 node を base.VisitFieldDeclaration() に渡してやることで、SyntaxTree 内にある node を変更後のもので置き換えることができます。変更する必要が無い場合は node をそのまま base.VisitFieldDeclaration() に渡します。

このサンプルコードでは、 アクセス修飾子が付いていない FieldDeclarationSyntax に対し、private を付加して base.VisitFieldDeclaration() に渡しています。


次に作成したクラスを使用し、実際にソースコードを変更する方のサンプルコードです。
パース対象のコード内にある RoslynSample. field1 に private を付加しています。

こちらで行うことは、先ほど作成したクラスの Visit() に SyntaxTree のルートノードを渡すだけです。Visit() の戻り値として変更後の SyntaxTree を取得できます。

変更後の SyntaxTree が表すソースコードを出力すると以下のようになります。


変更前はアクセス修飾子が無かった field1 に private が付いていますね!

と、まあこんな感じで、ソースコードをコーディング規約に従うように自動修正したりするような用途に SyntaxRewriter は便利です。

2014年9月21日日曜日

[Roslyn]SyntaxNodeの派生クラスでソースコードを変更してみる。

今回は SyntaxNode の派生クラスを使ってソースコードを変更してみます。

MethodDeclarationSyntax を使ってメソッドを変更する

まずは MethodDeclarationSyntax を使ってソースコードを変更してみました。
上側の RoslynSample_ModifyMethod.cs が MethodDeclarationSyntax を使ってソースコードを変更するサンプルコード、下側の RoslynSample_ModifyMethod_Output.cs が RoslynSample_ModifyMethod.cs で変更したソースコードを出力した結果です。 メソッド static HelloRoslyn() が static private NewHelloRoslyn() に変わっているのがわかると思います。 見てのとおり変更内容は
  • 名前を HelloRoslyn から NewHelloRoslyn に変更
  • private 修飾子の追加
の2点です。

サンプルコードの解説

上側の RoslynSample_ModifyMethod.cs について1行目から順に見ていきましょう。

1行目~25行目:
変更の対象となるソースコードです。

27行目:
ソースコードをパースして SyntaxTree を作成しています。サンプルでは文字列で定義したソースコードをパースするので ParseText() を呼び出しています。ファイルに記述されたソースコードをパースする場合、代わりに ParseFile() を呼び出します。

28行目:
SyntaxTree から MethodDeclarationSyntax 型のノードを抽出しています。

30行目~32行目:
MethodDeclarationSyntax 型のノードの中から識別子が "HelloRoslyn" のノードを取得しています。

34行目:
識別子が "HelloRoslyn" のノードが見つからなかった場合 targetMethod は null になるためガードを入れています。今回はパース対象のソースコードに識別子 "HelloRoslyn" のメソッドが必ず存在するので if 条件が false になることはありませんが、未知のソースコードをパースするような場合はこのようなガードが必要になります。

37行目~41行目:
ここで名前の変更と private 修飾子の追加を行っています。
まず WithIdentifier() で名前の変更をしています。 WithIdentifier() の引数に変更後の識別子 "NewHelloRoslyn" を渡すことで、識別子が "NewHelloRoslyn" の MethodDeclarationSyntax を戻り値として取得できます。
次に AddModifiers() で private 修飾子を追加しています。 AddModifiers() の引数には private 修飾子を表す SyntaxToken を渡していますが、 WithTrailingTrivia() で private の後ろに半角スペースを付加するようにしています。何故半角スペースを付加しているのかは後程解説します。

43行目:
ReplaceNode() を用いて変更後のノードで SyntaxTree 内の変更前ノードを置換しています。

44行目~46行目:
コードを整形しています。このサンプルのケースでは整形不要ですが、 SyntaxTree に新たにノードを追加した場合等はインデントが崩れるため、明示的にコードフォーマットを行う必要があります。

48行目:
変更後のソースコードを出力しています。

***DeclarationSyntax の Add***() と With***()

***DeclarationSyntax には、引数、属性、ステートメント等のノードを構成する要素を新規追加する Add***() という名前のメソッド群と、引数、属性、ステートメント等を変更する With***() という名前のメソッド群が定義されています。これらのメソッドでノードを構成する要素を変更(=ソースコードを変更)することができます。

これらのメソッドを使う時に注意しなければならないのが ***DeclarationSyntax が不変型であるという点です(というより、Roslynのクラスは基本的に不変型です)。すなわち、各メソッドはノード自身の値は変更せず、戻り値として変更適用後のノードを返すということです。 System.String 型と同じですね。そのためノードに対し複数の変更を行う場合、サンプルのようにメソッドチェーンで記述するかまたは逐次戻り値を変数に代入し、代入後の変数でメソッド呼び出しを行う必要があります。

Add***() の注意点

Add***() のメソッドを使う時に注意しなければならないのが、 Add***() は追加対象の部分しか追加してくれないということです。例えばサンプルにある AddModifiers() の場合、修飾子は追加してくれますが前後のスペースは追加してくれません。そのため、サンプルでは WithTrailingTrivia() で半角スペースを付加しています。 WithTrailingTrivia() を使わないと該当箇所は次のようなソースコードになってしまいます。

static privateNewHelloRoslyn()  // privateの後ろにスペースが無い!

WithTrailingTrivia() でスペースを追加する代わりに SyntaxNode.NormalizeWhitespace() を使うことで適切な位置(上記例の場合は private の後ろ)にスペースを追加することも出来ますが、メソッド前後の空行やインデントを削除しちゃったりする場合があるので使い勝手があまり良くないです。なので WithTrailingTrivia() を使うことの方が多くなると思います。


ClassDeclarationSyntax を使ってメソッドを追加する

次は ClassDeclarationSyntax を使ってクラスにメソッドを追加してみました。
上側の RoslynSample_AddMethod.cs が ClassDeclarationSyntax を使ってメソッドを追加するサンプルコード、下側の RoslynSample_AddMethod_Output.cs が RoslynSample_AddMethod.cs で変更したソースコードを出力した結果です。メソッド NewMethod() が追加されているのがわかると思います。

サンプルコードの解説

RoslynSample_AddMethod.cs について見ていきましょう。

1行目~27行目:
RoslynSample_ModifyMethod.cs と同じです。

29行目~30行目:
SyntaxTree から ClassDeclarationSyntax 型のノードを抽出し、その中から識別子が "Program" のノードを取得しています。

36行目~74行目:
ここでクラス Program に NewMethod() を追加しています。ご覧のとおりもの凄く大変です(^_^;)
Roslyn はこういうメソッドをまるまる追加といった用途に使うにはあまり向いてないですね。もしそれでも Roslyn でコードジェネレータのようなものを作りたいのならば、ラッパーメソッドを用意するかまたは文字列でコードを生成してから ParseText() や ParseExpression() 等でパースした方が良いでしょう。サンプルのような方法だとコンパイルが通るようにするのにも一苦労です。実際このサンプルも苦労しました(;^_^A

処理の内容は SyntaxFactory.MethodDeclaration() で MethodDeclarationSyntax を作成し、それを ClassDeclarationSyntax.AddMembers() に渡しているだけです。
特筆すべき箇所を挙げるとすれば NewMethod() の処理部分を作成している68行目~72行目でしょうか、 SyntaxNode.NormalizeWhitespace() を呼び出して適宜スペースを追加しています。このように Roslyn で新たにコードを追加する場合、スペースを必要に応じて追加してやる必要があるのが結構メンドイです(´Д`)

75行目~:
RoslynSample_ModifyMethod.cs と同じですが、こっちのサンプルではコードフォーマットの部分が必要です。コードフォーマットを行わないと追加したメソッド部分だけインデントが無い状態になってしまいます。

SyntaxNode の派生クラスでソースコードを変更してみてわかったこと

ソースコードの一部を変更するのは容易ですが新規にクラスやメソッドを追加するのはすごく大変ですね。特にコメントやスペースといった Roslyn で Trivia と表現される要素の扱いが難しい。 NormalizeWhitespace() の動作もリファレンスが無いため良くわからず、色々と試行錯誤する羽目になりました。


次回はソースコードを変更するもう1つの方法である SyntaxRewriter を使う方法でソースコードを変更してみます。

2014年8月14日木曜日

[Roslyn]SyntaxTreeを探索してみる

CSharpSyntaxTree.ParseText()の戻り値であるSyntaxTreeにはソースコードの様々な情報が格納されています。どんな情報が格納されているのか実際にサンプルソースをパースして確かめてみましょう。

SyntaxTree

今回パースしてみるのは以下のサンプルソースです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoslynSample
{
    /// 
    /// 氏名を表します。
    /// 
    class Name
    {
        private string m_firstName;
        private string m_lastName;

        /// 名を取得します。
        public string FirstName
        { 
            get { return m_firstName; }
        }

        /// 氏を取得します。
        public string LastName
        {
            get { return m_lastName; }
        }

        /// 氏名を取得します。
        public string FullName
        {
            get { return m_firstName + m_lastName; }
        }

        /// 
        /// 氏名を指定してNameクラスの新しいインスタンスを初期化します。
        /// 
        /// 名        /// 氏        public Name(string firstName, string lastName)         {             m_firstName = firstName;             m_lastName = lastName;         }          ///          /// 現在のオブジェクトを表す文字列を返します。         ///          /// 現在のオブジェクトを表す文字列。         public override string ToString()         {             return m_firstName + " " + m_lastName;         }     } } 
なんてことないValueObjectです。これをCSharpSyntaxTree.ParseText()でパースしてSyntaxTreeを取得し、中身を調べてみます。
SyntaxTreeは木構造なのでツリービューで表示してみました。 各ノードにはノードのクラス名とソースコード上の位置を表示しています。 見てのとおり、SyntaxTreeはCompilationUnitSyntaxをルートに様々な種類のクラスによって構成されています。

SyntaxTreeを構成するSyntaxNode

SyntaxTreeを構成するクラスは全てSyntaxNodeの派生クラスとなっています。SyntaxNodeは、ソースコード内におけるノードの位置やノードの文法上の種類を保持しており、またSyntaxTree内を渡り歩くためのメソッドも提供します。
SntaxNodeの主なプロパティとメソッドは以下の通りです。

表1. SyntaxNodeの主なプロパティ
プロパティ名説明
TextSpanFullSpanパースしたテキスト内におけるSyntaxNodeの範囲。FullSpan.Startがテキスト内でのSyntaxNodeの開始位置(0スタートの文字index)、FullSpan.LengthがSyntaxNodeの範囲長(文字数)を表す。
boolHasLeadingTriviaSyntaxNodeの前方にTrivia(コメント、改行文字、空白等)が存在するならtrue、それ以外はfalse。
boolHasLeadingTriviaSyntaxNodeの後方にTrivia(コメント、改行文字、空白等)が存在するならtrue、それ以外はfalse。
SyntaxKindKindSyntaxNodeの文法上の種類。
SyntaxNodeParent親のSyntaxNode
SyntaxTreeSyntaxTreeSyntaxNodeが含まれるツリーへの参照。


表2. SyntaxNodeの主なメソッド
メソッド名説明
Ancestors()先祖のSyntaxNodeのリストを取得。
ChildNodes()のSyntaxNodeのリストを取得。
DescendantNodes()子孫のSyntaxNodeのリストを取得。
GetLocation()SyntaxNodeの位置を取得。GetLocation().GetLineSpan()でSyntaxNodeが含まれるファイルのパスやSyntaxNodeの開始行が取得できる。
GetLeadingTrivia()SyntaxNodeの前方のTriviaを取得。
GetTrailingTrivia()SyntaxNodeの後方のTriviaを取得。
ToFullString()SyntaxNodeを表す文字列を取得(前後のTriviaを含む)。
ToString()SyntaxNodeを表す文字列を取得(前後のTriviaを含まない)。

メソッドには「Ancestors()」や「DescendantNodes()」といった LINQ to XML でお馴染みのメソッドがあります。ソースコード内のメソッド定義にアクセスする場合、これらのメソッドを用いて次のようにアクセスすることができます。
    var tree = CSharpSyntaxTree.ParseText(sourceCode);
    // メソッド定義を全て取得
   var methodDeclarations = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
同様に、クラスの定義には。OfType<Classdeclarationsyntax>()、プロパティの定義にはOfType<Propertydeclarationsyntax>()でアクセスできます。

SyntaxNodeの派生クラス

SyntaxNodeクラスの派生クラスはそれぞれがクラス宣言、メソッド宣言、foreach文、ブロック、式といった文法的な要素を表します。前項で出てきた「MethodDeclarationSyntax」はメソッド宣言、「ClassDeclarationSyntax」はクラス宣言、「Propertydeclarationsyntax」プロパティ宣言を表しています。これらのクラスは(C#の場合)Microsoft.CodeAnalysis.CSharp.Syntax名前空間に***Syntaxクラスとして定義されており、各クラスのプロパティを通して文法的な値(プロパティの型や名前等)を取得できます。また各クラスのメソッドを通して文法的な値を変更することも可能です。

次回はこのSyntaxNodeの派生クラスをいくつか取り上げ、実際に文法的な値を変更してみます。

2014年7月13日日曜日

[.NET]XmlSerializerでシリアライズするクラスって…

publicなコンストラクタを持ってなくてもいいんですね。ずっとpublicな引数無しコンストラクタが必須だと勘違いしてました。
MSDNを確認してみると…

XML シリアル化の概要 - XML シリアル化に関する考慮事項
  • クラスを XmlSerializer でシリアル化するには、そのクラスが既定のコンストラクターを持つ必要があります。
と書いてあり、どこにもpublicでないとダメだなんて書いてないんですよね。
まあ「既定のコンストラクター」っていうと、クラスにコンストラクタが定義されていない時にコンパイラが自動生成するpublicな引数無しコンストラクタを思い浮かべるから、それで勝手にpublicな引数無しコンストラクタが必須だと思い込んでたんでしょうねw

実際にpublicなコンストラクタを持っていないクラスをシリアライズ&デシリアライズしてみました。
using System.IO;
using System.Xml.Serialization;

namespace XmlSerializerSample
{
    public class SerializeTarget
    {
        private SerializeTarget () {}

        public SerializeTarget(int value1, int value2)
        {
            Prop1 = value1;
            Prop2 = value2;
        }

        public int Prop1 { get; set; }
        public int Prop2 { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SerializeTarget target = new SerializeTarget(100, 200);

            string serialized;
            XmlSerializer xs = new XmlSerializer(typeof(SerializeTarget));
            using (StringWriter sw = new StringWriter())
            {
                xs.Serialize(sw, target);
                serialized = sw.ToString();
            }

            SerializeTarget deserialized;
            using (StringReader sr = new StringReader(serialized))
            {
                deserialized = (SerializeTarget)xs.Deserialize(sr);
            }
        }
    }
}
この通りコンストラクタがprivateでもシリアライズ&デシリアライズできます。

2014年6月29日日曜日

[Tips]Grep置換

こんな方法でもGrep置換できるよーっていうTipsです。
まー大抵の場合エディタの機能を使ったり、linuxならコマンドで出来ちゃうのであまり使うことが無いかもですが、客先のPCにGrep置換できるエディタが入ってない!とかいった場合に使えるかもしれないので知っておいて損は無いかと。

方法は簡単、VisualStudioの置換を使います。
(エディタが入ってないのにVisualStudioが入ってるなんてありえるの?と思う人もいるかもしれませんが、そんな不思議があり得るんですよねー、仕事だと('A`))

VisualStudioでCtrl+Shift+Fを押すとフォルダを指定しての検索と置換ダイアログが出てくるので、置換タブを選択し、検索対象にGrep置換をしたいフォルダのパスをコピペして置換をするだけです。
「置換後に、変更したファイルを閉じない」にチェックを入れておけばCtrl+Zで元に戻すことも出来ます。

2014年6月22日日曜日

[Roslyn]とりあえず使ってみる

最初の発表からずいぶん目立った動きの無かったRoslynですが、最近になって動きが活発になってきました。なので、そんな流れに合わせて少しの間Roslynネタで記事を書こうと思います。

Roslynとは?

コンパイルの過程やコンパイラが持っている情報をAPIとして提供するコンパイラプラットフォームです。Roslynを使えば、C#のソースコードをパースしたり、メソッドや変数やの情報を取得できたりします。

つい最近公開されたVisualStudio14 CTPでRoslynが使用されていることからも、今後Roslynが.NETの中核になっていくのではないかと思います。要注目ですね!

※ちなみにRoslynはオープンソースとしてCodePlexで公開されています。

最近のMicrosoftはオープンで良いですねd(*´∀`*)

使ってみよう

早速Roslynを使ってみましょう。まずはNuGetでRoslynのパッケージをインストールします。VisualStudioのメニューから[プロジェクト]‐[NuGet パッケージの管理]を選択すると下記のダイアログが開きます。


左側の項目で「オンライン」を選択し、
1. 上部のコンボボックスで「リリース前のパッケージを含める」を選択
2. 右上にある検索ボックスに「Microsoft.CodeAnalysis」を入力
とすると、一番上に「Microsoft.CodeAnalysis」が出てくるので、これをインストールします。

インストールが終わった後にプロジェクトの参照設定を見ると、Microsoft.CodeAnalysis関係の項目が追加されています。

これでRoslynを使う準備は完了です。

C#のソースコードをパースしてみる

準備が出来たら実際にソースコードをパースしてみましょう。以下のコードがソースコードをパースするコードです(...と言ってもパースしてるのはMainの中の1行だけですけど(^_^;))。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// usingは以下の2つを追加
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace RoslynSample
{
    class Program
    {
        // パース対象のコード
        private static string m_sourceCode =
@"using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoslynSample
{
    class Program
    {
        /// 
        /// エントリポイントです。
        /// 
        /// コマンドライン引数        static void Main(string[] args)
        {
            System.Console.WriteLine(""Hello Roslyn!"");
        }
    }
}";
        static void Main(string[] args)
        {
            // ParseText()にパース対象のコードを渡せば、コードの構文木を作成できる。
            // コードをファイルで指定する場合はParseFile()を使用する。
            SyntaxTree tree = CSharpSyntaxTree.ParseText(m_sourceCode);
        }
    }
}

パースしたいソースコードをCSharpSyntaxTree.ParseText()に渡すだけです。
ParseText()の戻り値のSyntaxTreeは、ソースコードの構文を表した木構造になっていて、SyntaxTreeのノードであるSyntaxNodeにメソッドや変数の情報が格納されています。

次回はこのSyntaxTreeの中身を探ってみます。

2014年6月17日火曜日

再開準備中…

ずいぶんサボってましたが、ただいま再開準備中です(^∀^;)ゞ
サボってた間はC#でRoslynいじって遊んでました。なので、再開後はRoslynの記事が続く予定です。
Android開発はもうすぐスマホを買い換えるのでそれから再開する予定。
とりあえず今週末には1回更新します。