C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2008/08/03 00:15
NDepend というコードメトリクスツールがあります。
コードメトリクスツールとは、コードの品質を測定するツールです。品質を測定することで、コードの改善点を見つけ出し、リファクタリングに繋いでいきます。
例えば、20行を超えるメソッドを (ツールで) 見つけ出し、メソッドの細分化を (プログラマが) 行う、といった具合です。

NDepend は有償ですが、それだけの価値があるツールです。
実際に見てみましょう。


0. ソリューションの用意

新規作成直後のコンソールアプリケーションプロジェクトが 1 つだけ含まれるソリューションを用意してください。今回はこのソリューションを測定します。


1. NDepend プロジェクトの作成

まず、NDepend プロジェクトを作成します。
ツールメニューの [File] - [New Project] をクリックすると、NDepend プロジェクトの名前と保存場所を設定するダイアログが表示されます。



名前と保存場所を入力して [OK] ボタンをクリックすると、NDepend プロジェクトが作成され、画面が次のように変化します。




2. 測定対象アセンブリの設定

次に、測定対象のアセンブリを設定します。
設定の方法はいくつかあるのですが、今回はソリューションファイルで指定します。
[.dd Assemblies of a VisualStudio Solution] ボタンをクリックすると、ファイル選択ダイアログが表示されますので、ソリューションファイルを選択し [OK] ボタンをクリックしてください。



ソリューションファイルで指定すると、ソリューションに含まれている各プロジェクトの出力アセンブリが自動で追加されます。出力アセンブリが見つからないとアセンブリ名の左にエラーマークが表示されます。その場合は、ソリューションのビルドをしてから [Reflesh] ボタンをクリックしてください。




3. 分析の実行

測定対象を設定したら、とりあえず分析を実行します。
ツールメニューの [Analysis] - [Run Analysis on Current Project] をクリックすると分析が開始します。
コンソール画面が表示され、しばらく経つとブラウザでレポートが開かれます。



スクロールするとわかりますが、色々な情報が出力されてます。 (この記事では、これらを細かく解説しません。)


4. CQLのカスタマイズ

NDepend では、測定に CQL という独自のクエリーを使用します。この CQL は SQL に近い文法で、とても簡単に扱うことができます。先ほどの例で挙げた「20行を超えるコードメソッド」を見つけ出すには、

SELECT METHODS WHERE NbLinesOfCode > 20

といった CQL を書きます。 (この記事では、CQL について細かく解説しません。)
では、NDepend に戻ってください。



画面が分析実行前とは違う画面に変化しています。これが NDepend のメイン画面です。
複数のビューがありますが、最も重要なのは画面下の CQL Queries ビューです。ここで CQL の管理を行うわけです。
NDepend では、最初からたくさんの CQL が用意されてますが、今回はこれらの CQL を全て削除してしまいましょう。CQL Queries ペインの左部に列挙されているカテゴリを、[Delete Group] ボタンにて一つずつ削除してください。 ("Constraints extracted from Source Code" は削除できません。)



では、「20行以上のコードで記述されているメソッド」を見つけ出す CQL を追加しましょう。
まず、[Create Group] ボタンにてカテゴリを作成します。今回は名前はそのままでいいです。
次に、[Create Query] ボタンにて CQL を作成します。すると CQL Queries ビューが CQL エディタに切り替わります。



ここで、「20行を超えるメソッド」を見つけ出す CQL を記述するのですが、先ほどの CQL をそのまま記述すると、「20行を超えるメソッド」が見つかっても警告されません。なので、先ほどの CQL の先頭に "WARN IF Count > 0 IN" を加えた CQL を記述してください。これで、1 件でも見つかったら警告されます。
それと、コメントで Name という XML 要素を記述すると、CQL に名前を付けられますので、"Test CQL" と記述しておいてください。

// <Name>Test CQL</Name>
WARN IF Count > 0 IN SELECT METHODS WHERE NbLinesOfCode > 20



記述しましたら、[Save changes] ボタンにて保存します。


5. 測定結果の確認



#Items 欄を見てください。CQL の該当件数が表示されています。CQL の実行は即座に行われ、分析を再実行する必要はありません。
「20行を超えるコードで記述されているメソッド」は現在 0 件のようです。ではここで、ワザと 20 行を超えるコードを用意しましょう。Main メソッドに Console.WriteLine() を 30 行分記述してください。
コードを記述してビルドを行ったら、再び分析を実行してください。すると、レポートが表示されますが、これはひとまず置いておいて、 NDepend にすぐ戻って #Items 欄を見てください (分析結果をロードするかどうかの確認ダイアログが表示されますが OK をクリックしてください。)。



今度は 1 件見つかってます。また、カテゴリと CQL に警告マークが表示されています。
CQL にフォーカスして、左上の CQL Query Result ビューをみてください。ここには、選択した CQL に該当するメソッドが表示されます。

これで、「20行を超えるコードで記述されているメソッド」を検出することができるようになり、しかも該当メソッドが一目でわかるようになったわけです。あとは、検出されるたびに該当メソッドをリファクタリングすることで高い品質を保つことができるというわけです。

先ほど後回しにしたレポートも見てみましょう。[CQL Queries and Constraints] という欄までスクロールしてください。



こちらでも、同様のことが確認できますね。カテゴリと CQL は (警告を表す) 黄色で表示されてます (CQL は警告のみが列挙されます)。また、該当のメソッドも確認できます。


[ 最後に ]

CQL は実に様々な条件を表現できますし、習得は本当に容易です。また、今回はわかりやすくするために削除してしまいましたが、標準でたくさんの CQL が用意されています。今回記述した CQL も、実は標準で用意されてたりします (20 行ではなく 30 行ですが)。
それと、NDepend は Cruise Control.NET などの継続的インテグレーションツールと連携させることができますので、定期的且つ自動的なコード測定も可能です。

NDepend を使えばコードの品質を保ち易くなります。是非、活用してください。
2008/07/16 00:26
日本の PowerShell 情報発信サイト 「PowerShell from Japan!!」 が先日オープンしました。

PowerShell from Japan!! - ここが日本のPowerShell情報発信基地

立ち上げたのは HIRO's .NET の HIRO さんです。
このサイトは、複数の Author がブログ形式で PowerShell の情報を発信するサイトで、今後、日本の PoSHer (PowerShell 好きな人々) に欠かせないサイトになると予想されます。
ちなみに、僕も Author として参加しています。


「PowerShell from Japan!!」では、Author 募集中です。
PowerShell の情報を発信したいという方、是非 Author として参加してみてください。
参加をご希望の方はこちらのフォームからその旨ご連絡お願いします。
# フォームから送信した内容は HIRO さんの元へ届きます。
タグ: .NET PowerShell
2008/07/06 22:47
フィールド、プロパティ、メソッドの戻り値・パラメータには List<T> を (原則) 使用しない方がいいかもしれない。
代わりに IEnumerable<T>、IList<T>、ICollection<T>、Collection<T> のいずれかを使う。


[ 理由 1 ]
List<T> はパフォーマンス向上のために使用するコレクションで、継承は想定されていない。一方、Collection<T> は、継承して独自のコレクションを定義することが可能な (想定されている) コレクション。
このため、外部に公開するプロパティや外部に公開するメソッドの戻り値・パラメータで List<T> を使用することは推奨されていない
僕は 「List<T> は多態性を持てないから」 と勝手に解釈している。


[ 理由 2 ]
Collection<T> は、IList<T> を受け取るバージョンのコンストラクタを使用することで、コレクションのラッパーとして機能させることができる。すると、Collection<T> 型のフィールドには、仮想プロキシによるレイジーロードなど、別のコレクションを注入することができる。
# List<T> では、IEnumerable<T> を受け取るコンストラクタがあるが、これはコンストラクタ内で要素のコピーを行っており、ラッパーとしては働かない。


[ 注意点 ]
ひとつ気をつけなければならない点として、List<T> は独自の機能を豊富に提供していることが挙げられる。例えば Collection<T> のインスタンスで List<T> の機能を使いたい時は、List<T> に変換する (IEnumerable<T> を受け取るコンストラクタを使う) などしなければならない。
タグ: .NET C#
2008/07/03 21:17
既にご存知の方も多いかと思いますが、HIRO さん執筆の「Windows PowerShell 入門」という連載記事が CodeZine に掲載されています。

# CodeZine では、一定期間を過ぎた記事の一部が、会員でないと閲覧できなくなりますので注意してください。
# 会員登録はこちらから無料で行えます。

Windows PowerShell 入門(1)−基本操作編:CodeZine
Windows PowerShell 入門(2)−基本操作編 2:CodeZine
Windows PowerShell 入門(3)−スクリプト編:CodeZine
Windows PowerShell 入門(4)−変数と演算子:CodeZine
Windows PowerShell 入門(5)−制御構文:CodeZine
Windows PowerShell 入門(6)−関数編1:CodeZine
Windows PowerShell 入門(7)−関数編2:CodeZine

で、本日掲載された第7回目の記事で、当ブログのプロファイル活用という記事を紹介して頂きました。
このテクニックは僕自身気に入ってまして、是非多くの方に知ってもらいたいと思っていました。HIRO さん、ありがとうございます m( _ _ )m

ところで、これまたご存知の方も多いかと思いますが、HIRO さんのサイトブログでは、PowerShell の Tips がたくさん紹介されています。
PowerShell に興味のある方、CodeZine の連載と併せてチェックしておいた方がいいと思いますよ (^ω^ )
タグ:
2008/06/21 03:07
C#と諸々 Nullable 型のボックス化


GetType メソッドは Object クラスで定義されている非仮想メソッドなので、GetType メソッドを値型に対して呼び出す際はボックス化が必要


と書きましたが、もうちょい詳しく書いときます。
# 今回の記事は Nullable 型に限らない話です。

[ Object クラスに定義されている非仮想メソッド ]
Object クラスには他にも非仮想メソッドとして MemberwiseClone メソッドが定義されていますが、このメソッドを呼び出す場合も同様にボックス化が発生します。
ポイントは次の 2 点です。

・ インスタンスメソッドは自分自身を取得できる
インスタンスメソッドでは、this キーワードにて自分自身を取得できます。しかしこれは、C# の話であって、IL レベルでは、this キーワードに相当する機能は用意されていません。
ではどうやって自分自身を取得するかというと、実はメソッドの 0 番目の引数として自分自身が渡されます。

・ 非仮想メソッドはオーバーライドできない
非仮想メソッドを派生クラスがオーバーライドすることはできません。つまり、GetType メソッドはオーバーライドされていないわけです。GetType メソッドは Object クラスに定義されています。

この 2 点を踏まえて考えてみてください。
GetType() メソッドの 0 番目の引数の型、つまり GetType メソッドにおける this の型は何型でしょう?
これがボックス化の理由です。
答えは Object 型ですね。


[ コンストラクタについて ]
値型は ValueType クラスを継承します。ValueType クラスはあくまでも参照型です。
値型にはコンストラクタを定義できます (C# の場合引数を持たないコンストラクタを値型に定義することはできません)。通常、コンストラクタは基底クラスのコンストラクタを呼び出さなければいけません。そしてコンストラクタは非仮想メソッド (正確には継承されないメソッド) です。ということは、コンストラクタが呼び出されるとボックス化が発生するのでしょうか?
答えは「発生しない」です。これは、値型のコンストラクタは基底クラスのコンストラクタを呼び出さないからです (これが CLR で決められているルールなのかはちょっとわからなかったです)。


[ ValueType クラスでオーバーライドされているメソッドについて ]
「プログラミング Microsoft .NET Framework 第2版」によると、ToString メソッドや Equals メソッド等の、ValueType クラスでオーバーライドされているメソッドに対してはボックス化が行われないと書いてあります。でも、ValueType クラスは参照型です。ホントにボックス化されないのでしょうか?
答えは「される」です。つまり、「プログラミング Microsoft .NET Framework 第2版」に書かれているのことは正しくないです。やはり、ValueType は参照型だからボックス化されるのです。

当然ですが、値型自身が更に ToString メソッドをオーバーライドしている場合は、ボックス化はされません。0 番目の引数の型は値型ですので。


以降、このことについて MSIL レベルで説明をします。
まず、Int32 の値に対して ToString メソッドを呼び出すコードを見てみます。

static void Main(string[] args)
{
    int a = 10;
    a.ToString();
}

このコードは次のような MSIL コードにコンパイルされます。

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 a)
    L_0000: ldc.i4.s 10
    L_0002: stloc.0
    L_0003: ldloca.s a
    L_0005: call instance string [mscorlib]System.Int32::ToString()
    L_000a: pop
    L_000b: ret
}

L_0003 と L_0005 をみると、ローカル変数 a のマネージポインタをプッシュして Int32.ToString メソッドを呼び出しています。これは特に変わったところもありません。ローカル変数 a に対して ToString メソッドを呼び出すごく普通の MSIL です。(ちなみに、マネージポインタをプッシュしている理由ですが、マネージポインタではなくローカル変数 a を直接プッシュしてしまうとローカル変数 a のコピーがプッシュされてしまうためです。)


では、次のようなコードはどうでしょうか。

static void Main(string[] args)
{
    Hoge h = new Hoge();
    h.ToString();
}

struct Hoge
{
}

このコード (の Main メソッド) は次のような MSIL コードにコンパイルされます。

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype Program/Hoge h)
    L_0000: ldloca.s h
    L_0002: initobj Program/Hoge
    L_0008: ldloca.s h
    L_000a: constrained Program/Hoge
    L_0010: callvirt instance string [mscorlib]System.Object::ToString()
    L_0015: pop
    L_0016: ret
}

L_0008, L_000a, L_0010 を見てください。
L_0008 については、先ほどのコードの L_0003 と同様ローカル変数 h のマネージポインタをプッシュしています。問題は次の L_000a と L_0010 です。

まず L_0010 ですが、Hoge.ToString を call 命令で呼び出すのではなく、 Object.ToString を callvirt 命令で呼び出しています。
もし、Hoge.ToString を call 命令で呼び出していれば、CLR は最初から Hoge.ToString を使用することを知ることができます。
しかし、Object.ToString を callvirt 命令で呼び出しているということは、遅延バインディング (実行時に変数 h の型を調べて適切なメソッドを判断) するということになります。

次に (一行戻って) L_000a ですが、この constrained が鍵を握っています。これは callvirt 命令にだけ付加することができるプリフィックスです。このプリフィックスでは、一緒に型を指定します (以降、thisType と記述)。
thisType が値型の場合、次の 2 つのケースでそれぞれ異なる動作をします。

  1. tisType が callvirt 命令で指定されているメソッドを実装していない場合
  2. tisType が callvirt 命令で指定されているメソッドを実装している場合

今見ている Hoge 構造体は、ToString をオーバーライドしていませんので 1 に該当します。
この場合、ローカル変数 h はボックス化されて ToString メソッドに渡されます。まぁ、先ほども言ったように、ValueType クラスは参照型ですから当然です。そして、Object.ToString を callvirt 命令で呼び出したことになります。つまり、メソッドの遅延バインディングが発生します。


では、Hoge 構造体で ToString メソッドをオーバーライドして再コンパイルしたとします。その場合も Main メソッドの MSIL コードは変わりません。すると 2 に該当することになります。
この場合、ローカル変数 h はボックス化されずに ToString メソッドに渡されます。これも適切な動作です。また、Hoge.ToString メソッドを call 命令で呼び出した場合と同じ動作をします。つまり、メソッドの遅延バインディングが発生しません。


なぜ、Int32 値の ToString メソッドを呼び出すコードでは、constrained プリフィックスが使用されなかったのでしょうか?
恐らくそれは、バージョン問題が発生しないからです。
今回の Hoge 構造体のように、ユーザー定義の構造体のインスタンスに対して、ValueType クラスでオーバーライドされているメソッドを呼び出す場合はバージョンの問題が発生する可能性があります。
たとえば、最初は Hoge.ToString を定義せずにコンパイルしたが、後から Hoge.ToString を定義 (オーバーライド) して再コンパイルした場合、利用側のコードはコンパイル前と後で異なる動作をしなければなりません。もし constrained プリフィックスがなければ、利用側のコードも再コンパイルしないといけなくなるわけです。
というわけで、constrained プリフィックスのおかげで、再コンパイルせずに動作を切り替えられるわけです。


# すごい疲れた


[ 参考 ]
OpCodes.Constrained フィールド (System.Reflection.Emit)
タグ: .NET C# CLR