連載:C# 3.0入門第5回 拡張メソッド株式会社ピーデー 川俣 晶2008/08/08 |
|
|
メソッド呼び出しと型の関係
拡張メソッドを呼び出す場合、注意すべき点は、異なるパラメータを持つメソッドは別のメソッドと見なされるという点である。つまり、同じ名前のメソッドがすでに存在するため拡張メソッドを呼び出せないケースでも、パラメータの型を変えると呼び出せてしまうケースが存在する。
また、その型を包含する基本的な型を持つパラメータがあれば、異なる型でも拡張メソッドは呼び出されないかもしれない。
それを具体的に見るために、以下のサンプル・コードを作成した。
| ||||||||||||
リスト10 メソッド呼び出しと型の関係 | ||||||||||||
|
このような特徴は、例えば引数を持つToStringメソッドが存在しないクラスに対して、書式指定の引数を持つToStringメソッドを追加する、といった使い方が可能であることを示す。
以下は、クラスに書式指定機能付きのToStringメソッドを追加した例である。引数に「u」を付けた場合は、型名を大文字に変換して返す。「l」の場合は小文字にして返す。
| |
リスト11 クラスに書式指定機能付きのToStringメソッドを追加 |
もちろん、このような事例は、元クラスを修正してメソッドを追加するか、継承を用いてメソッドを追加するのが筋である。拡張メソッドを使わないで済めばその方がよい。
しかし、ソースを修正できず、シール・クラスなどの理由により継承も封じられた状況で、これが可能であるのはありがたい特徴ではないだろうか。
thisの正体
拡張メソッドを宣言する際には「thisキーワード」を使うが、これはインスタンスのメソッドが自分自身を指し示すために使うthisキーワードと同じ名前である。これは、異なる役割に同じキーワードを割り当てているのではなく、役割が同じであるから同じキーワードを割り当てているものである。
以下は、通常のメソッドと拡張メソッドで同じ機能を記述した例である。とで、thisキーワードが付加されたパラメータ(this B t)と、「this.Number」のthisがまったく同じように使われていることが分かるだろう。
| |
リスト12 thisパラメータとインスタンス参照のthisの役割は同じ |
このような、明示的にthisを渡すスタイルは、実はコンパイラの内部実装を明示的に書き直したもの、と見なすこともできる。
例えば、インスタンスやthisという概念がないC言語の関数と、それらの概念があるC++言語のインスタンス・メソッドのマシン語レベルにおける実装面での機能差は、主にthisという概念の有無に求められる。そして、thisとは具体的には「暗黙的に追加されるもう1つのパラメータ」として実装することができる(最近のC++コンパイラはもっと効率的な実装を行っているようであり、この説明に当てはまらない)。そのため、C++言語処理系の中には、インスタンス・メソッドを、thisポインタを引数に追加した関数として呼び出せるものがあったように記憶する(かなり強引に行う必要があり、普通に記述してもできないが)。
このような背景から考えると、拡張メソッドとは本来「暗黙的に渡されるはずのthisを、明示的に引き渡すように宣言されたメソッド」と見ることもできる。
ちなみに、静的なメソッドはthisを持たない。そのため、C++の静的なメソッドと、C言語の関数は(いくつかの小さな相違点を除き)同じと見なせる。それ故に、C言語の関数ポインタを渡すWindows APIには、C++のインスタンス・メソッドは渡せないが、静的なメソッドなら渡せることがある。
このような「thisを持たない」という性質は、C#の静的クラス、静的メソッドにも共通する。thisを明示的に受け取る拡張メソッドが、静的クラスかつ静的メソッドでなければならない理由はここにある。thisを明示的に受け取る以上、暗黙的に受け取るthisがあるとトラブルのもとである。
拡張メソッドを使用すべきとき
さて、拡張メソッドはどのような場合に使用すべきだろうか。逆に、どのような場合には使えないのだろうか。
まず、拡張メソッドは「メソッド」しか存在しないことを強調しておこう。プロパティやインデクサを拡張することはできない。あくまで「メソッド」だけである。このことだけで、拡張メソッドに期待された役割が限定された狭い範囲でしかないことが分かるだろう。可能であれば、拡張メソッドは使わない方がよい。
また、拡張メソッドでは記述できない処理も多いことに注意を払おう。拡張メソッドは、あくまでオブジェクト外の存在であり、オブジェクト内部に手を出すことができない(リフレクションを使えば強引に割り込めるが、あまりお勧めではない)。
では、そもそも、なぜ拡張メソッドは必要とされたのだろうか。
その答えは、恐らく主にLINQにある。LINQは統一されたクエリを実現するために、既存コレクション群への拡張を必要としていた。しかし、LINQはオプションであり、常に使うものではない。うかつな拡張は、既存のソース・コードとの互換性を失わせるかもしれない。そのように考えると、以下のような特徴を持つ拡張メソッドは優れた選択であることが分かる。
- 既存のクラスを変更しない
- インポートしない限り有効にならない
- インポートすれば有効になるので、既存コードにすぐLINQの機能を追加できる(もし継承で実現していたら、追加機能を使うためにクラス名を書き換える必要が生じる)
逆にいえば、普通の手順で開発されているプログラムであれば、このような特殊事情はあまり見られず、拡張メソッドを使うメリットも見えにくい。
しかし、既存のクラス・ライブラリに対して、「このメソッドがあれば便利なのに」と思うことはあるだろう。例えば、文字列に対して、ある業務で使用される特殊なハッシュ・コードを計算する処理が多発するなら、そのような計算を行うメソッドをstringクラスに追加してしまうのは有用だろう。そのような状況に対処する方法として、拡張メソッドは優れている。
コレクションに拡張されるメソッド
MSDNのリファレンスでは、拡張メソッドによって実現されたメソッドは、通常のメソッドとは別にまとめられている。コレクションであるList<T>クラスなどのメンバ一覧を見ると、「メソッド」の下に別途「Extension のメソッド」という項目がある(版が違うと表記が違うかもしれない)。これが拡張メソッドである。
ちなみに、Extensionの訳語は揺れているようで、「拡張」メソッドと表記されることもあれば、このように「Extension」のメソッドと表記されることもある。さらに、IntelliSenseを発動させると「拡張子」と表示されることもあるが、これは誤訳であろう(ちなみに、ファイルの拡張子は英語で「filename extension」と呼ぶので、文脈を考えずに単語単位で訳しての誤訳であろう)。
さて、ここではList<T>クラスを例にして、どのようなメソッドが拡張されているか見てみよう(ほかのコレクションのクラスも同様に拡張されている)。
まず、これらのメソッドの多くは、実はList<T>クラスに対して拡張されて“いない”ことに着目しよう。実際には、List<T>クラスが実装しているIEnumerable<T>インターフェイスに対する拡張として定義されている。拡張メソッドの実装そのものは、System.Linq.Enumerableクラスに存在する。
この事実は、実はLINQが「コレクション」ではなく「列挙」に対して作用する機能であることを示しているが、これはLINQを解説する際にあらためて取り上げよう。
さて、以上のような前提を踏まえて、List<T>クラスの拡張メソッドの一覧を見ていこう。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
List<T>クラスの拡張メソッド一覧 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
なぜ「using System.Linq;」なのか?
さてここまで見て、ぜひ使いたいというメソッドが見つかった読者もいると思う。しかし、LINQまでは難しくて手が出ないと思った読者もいるだろう。そういう読者は、LINQは使わないのにこれらのメソッドを使うために、「using System.Linq;」と書き込む必要があることに釈然としない感じを受けるかもしれない。しかし、これらは紛れもなくLINQを構成する一部なのである。
LINQといえば、
from 〜 in 〜 where 〜 select 〜
といったクエリ式を連想するかもしれないが、これはLINQのクエリ構文と呼ばれるものである。しかし、LINQにはこれとは別にメソッド構文というものがあり、メソッド呼び出しの連鎖としてクエリを記述できる。その際、上記リストにあるWhereメソッドなどが「まさにそのままメソッドとして」使用される。
つまり、これらのメソッドを使い始めたあなたにとって、LINQは縁遠いものではない。むしろ、すでにLINQの世界に片足を踏み込んでいるのである。
次回予告
そのようなわけで、次回はLINQの世界に足を踏み入れていこう。
筆者も日常的にLINQのクエリ式を書いてはいるが、LINQのすべての機能を使ったとは、到底いいにくい。これを機会に、筆者としてもLINQに対して全面的に取り組んで、その全貌を明らかにしてみたいと考えている。
INDEX | ||
C# 3.0入門 | ||
第5回 拡張メソッド | ||
1.C# 2.0プログラマーの悲劇 | ||
2.拡張メソッドの概要/スイッチなしで機能する例/sealedクラスを拡張する | ||
3.メソッド呼び出しと型の関係/thisの正体/拡張メソッドを使用すべきとき | ||
「C# 3.0入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|