拡張メソッド(Extension Methods)-クラス外部からのメソッドの追加
拡張メソッドの機能を使うと、クラスの外部からクラスにメソッドを追加できる。
Rubyにもextendメソッド( extend (Object) - Rubyリファレンス )というメソッドが存在するがこれはまた別物である。
通常、オブジェクト志向言語では、クラスにメソッドを追加するにはその対象のクラスもしくはそのスーパクラスにメソッドを追加する必要がある。
しかし、C#の拡張メソッドの仕組みを利用すると、追加対象のクラスのソースに一切修正を加える事無く、外部のstaticクラスを使って他のクラスにメソッドを追加できる。
まずは、簡単なサンプルから
以下のようなクラスがあったとする。リスト1-1(ExtensionMethodsSample1.cs)
上記、SampleClass1のソースに手を加える事なく、SampleClass1クラスに新たにSampleExtensionMethod1というメソッドを追加してみる。
問題を簡単にするために、まずは、引数、戻り値を持たない単純なメソッドを追加してみる。
プロジェクトに新たに、以下のようなstaticクラスを追加する。
(staticクラスについては別稿にて解説の予定)
このstaticクラスに実装したSampleExtensionMethod1メソッドは、以下のようにSampleClass1クラスのメソッドとして実行できる事になる。
ここで、Hooクラスに新しくメソッドを追加しているにもかかわらず、list1_3のコードにHooというクラス名は使われていない。
つまり、拡張メソッドを実装するクラスはstaticクラスであれば、元のクラスと縁もゆかりも無い任意のクラスでかまわないという事になる。
また、追加されたメソッドの第一引数には、thisキーワード付きでメソッド追加対象となるクラスが指定されている点が拡張メソッドの特徴である。(リスト1-2の6行目)
引数付きの拡張メソッド
次に戻り値、引数付きの拡張メソッドをリスト1-2のHooクラスに追加するコードを以下に示す。
メソッドの引数を第2引数以降に追加しただけで、リスト1-2のSampleExtensionMethod1メソッドのコードと何ら変わりはない。
拡張メソッドのからくり
拡張メソッドは、以下のようにstaticクラスHooのメソッドとしてアクセスする事も可能である。
これは、通常のthisの付かないstaticメソッドに対する呼び出し方と同じである。
どうやら、拡張メソッドの実態はシンタックスシュガーようである。
以下のように。
と記述すると、コンパイラのほうで、自動的に
と読み替えてコンパイルを実行してくれるという事のようだ。
コンパイラにどうか自動的に読み替えてコンパイルして下さいとお願いするためのマークとして、thisキーワードを記述するというルールになっている事のようだ。
thisキーワードは通常、クラス内部でそのクラス自身のインスタンスを指すために使われるが、 ここでは、staticクラスにはインスタンスは存在しないけれども、thisキーワードの後に指定された引数をそのクラスのインスタンスのように使って下さいよ、という事を示していると思われる。
名前空間にも注意。
拡張メソッドを参照する側の名前空間と別の名前空間に拡張メソッドが定義されている場合には、useingを使ってその名前空間を参照できるようにしておく必要がある。
これは、拡張メソッドがシンタックスシュガーによるstaticクラスの置き換えであると考えると、 当然、useingを使って拡張メソッドが定義されている名前空間を参照できるようにしておく必要がある事は理解できるだろう。
拡張メソッドの性質
拡張メソッドは拡張先のクラスのpraivateメンバーにアクセスできない。
これも、拡張メソッドの実態が単なるstaticメソッドの置き換えであると考えれば当然の事である。
要は、praivateメンバーに限らず外部のstaticクラスからアクセスを許されるものだけが利用できるという事である。
拡張メソッドを多用しない。
拡張メソッドはクラスの外部からでもクラスのメソッドを拡張できるので便利なのだが、拡張メソッドを実装するstaticクラスは元のクラスとは縁もゆかりもないクラスなので、 どこにそのコードが実装されているのか把握しずらいという欠点があり、乱用すると読みにくいプログラムになってしまう。
このため、むやみに使うのでは無く、ポイントをおさえて必要なときにのみ使うようにした方が良い。
拡張メソッドの出番は、ソースコードをさわれないクラスをどうしてもやむおえず拡張したい場合に、限定すべきである。
拡張メソッドは上書きされない
もとのクラスに既に実装済みのメソッドに対して、同じメソッドを拡張メソッドとして追加した場合はどちらのメソッドが有効になるのだろううか?
それを確認するためにリスト1-1のSampleClass1クラスに拡張メソッドと同じメソッドを追加してみた。
リスト4(ExtensionMethodsSample1.cs)
結果としては、もとのクラスのメソッドが優先される。
念のため、もとのクラスのスーパクラスに存在するメソッドを拡張メソッドとして追加した場合の動作も確認してみた。
リスト5-拡張メソッドよりもスーパクラスのメソッドが優先される
これも、もとのクラスの(スーパクラスの)メソッドが優先される。
もとのクラスのメソッドを拡張メソッドでオーバロードする事は許される?
では、クラスに既に実装済みのメソッドをオーバロードした(引数の型や数が異なる)メソッドを、拡張メソッドで追加した場合はどうなるのだろう。
以下はその例である。
拡張メソッドでオーバロードしたメソッドもちゃんと実行される。
拡張メソッドは継承される?
継承関係もコンパイラが賢く判断してstaticメソッドに置き換えてくれるようだ。インターフェースに対しても拡張メソッドを定義できる。
インターフェースにも拡張メソッドを追加できる。
以下はその例である。
拡張メソッドの応用
.NETフレームワークで定義済みのクラスを拡張する。
システム(.NETフレームワーク)で定義済みのクラスは、通常、sealedクラスになっていて継承関係を使ってクラスを拡張してメソッドを追加する事ができない。
例えば、以下のようなコードはコンパイルエラーとなる。
このような場合は、拡張メソッドの出番である。
拡張メソッドはsealedクラスにメソッドを追加する事もできる。
すべてのクラスで使用できるメソッドを実装する。
Objectクラスはすべてのクラスのスーパクラスなので、Objectクラスの拡張メソッドを実装すれば、すべてのクラスで使えるメソッドとなる。
参考URL