一般に、元のクラスに変化を加えること無くメソッドを追加したい場合、派生クラスを作成してそのクラスでメソッドを追加することになります。 拡張メソッド(extension methods)を使うと、元の型への変更をせず、かつ直接継承関係にない型からメソッドを追加することができます。 追加といっても実際に型に対してメソッドが追加されるのではなく、型の外部で宣言された静的メソッドを、あたかもインスタンスメソッドのように呼び出すことができるようにするのが拡張メソッドです。 拡張メソッドを宣言すると、見かけ上は型にメソッドが追加されたように見えます。
例えば次のような静的メソッドがあったとします。 これを拡張メソッドにすると、あたかもインスタンスメソッドであるかのように呼び出すことができます。
このように、拡張メソッドを用いると既存の型に一切変更を加えずにメソッドを追加できます。 また、拡張メソッドとして宣言すると、静的メソッドをインスタンスメソッドのように呼び出すことができます。 拡張メソッドは、値型など継承することができない型に対してもメソッドを追加することができます。
拡張メソッドの使用
拡張メソッドを使った代表的な例にLINQがあります。 System.Linq
名前空間をインポートすることにより、LINQの拡張メソッドが使えるようになります。 例えば、次の例で使用しているAverageメソッドはIEnumerable<T>
インターフェイスに対して追加される拡張メソッドです。
このAverageメソッドは本来IEnumerable<T>
やList<T>クラスには存在しないものですが、List<T>
クラスに追加されたメソッドのように使用することができます。
拡張メソッドはインスタンスメソッドのように呼び出せる点をのぞくと、実体は単なる静的メソッドです。 例えば上記のAverageメソッドも実際にはEnumerableクラスで定義されている静的メソッドです。 このため、次のように静的メソッドとして呼び出すこともできます。
拡張メソッドの宣言
クラスライブラリで提供されるもの以外にも、拡張メソッドを独自に宣言して追加することもできます。 拡張メソッドは、静的メソッドを宣言するときとさほど変わらない方法によって宣言することができます。
C#では、メソッドの第一引数にthis
キーワードを前置すると、その引数の型に対する拡張メソッドとして公開することができます。 たとえばthis string val
という引数を記述するとstring
型に対する拡張メソッドとなります。 一方VBには拡張メソッドを宣言する専用のキーワードは用意されておらず、メソッドにExtension属性(System.Runtime.CompilerServices名前空間)を付与することで拡張メソッドを宣言します。
拡張メソッドは静的クラス(static class
、VBではモジュール)で宣言する必要があります。 第一引数でメソッドを追加する型を定めるため、拡張メソッドは必然的に引数が一つ以上のメソッドとなります。
this
キーワードを使って宣言した拡張メソッドには、コンパイル時に自動的にExtension属性が付与されます。 .NET Framework 4以前の場合、Extension属性はSystem.Core.dll
で宣言されているため、System.Core.dll
を参照に追加する必要があります。
なおC#では、VBのようにExtension属性を指定することによって拡張メソッドを実装することはできません。 次のようにコンパイルエラーとなります。
引数のある拡張メソッド
拡張メソッドの場合もほかのメソッドと同様に引数を渡すことができます。 第一引数には拡張メソッドの呼び出しを行ったインスタンス、第二引数以降には拡張メソッドの呼び出しに指定された引数が渡されます。
インターフェイス・列挙体の拡張メソッド
拡張メソッドはクラス・構造体のほか、インターフェイスや列挙体に対しても追加することができます。 次の例では、列挙体に拡張メソッドを持たせるようにしています。
この例の場合、IsReadOnly
はメソッドではなくプロパティとする場合が多いですが、既存の型にプロパティを追加する拡張プロパティのような機能はなく、またそういったプロパティを宣言することもできないため、拡張メソッドとして宣言しています。
LINQはインターフェイスへ拡張メソッドを追加する最たる例です。 LINQでは、IEnumerable<T>インターフェイスに追加される拡張メソッドがEnumerableクラスで多数宣言されています。
LINQの拡張メソッドがどのように実装され、機能するかについてはLINQ:実装して理解するで解説しています。
拡張メソッドを宣言するクラスの名前
拡張メソッドを宣言するクラスには任意の名前を使用することができます。 必ずしも拡張メソッドを追加する型と関連する名前となっている必要はありませんが、拡張メソッドを含むクラスであることをわかりやすくするためにXxxExtension(s)
というクラス名(string
型に対する拡張メソッドを追加するクラスならStringExtension
など)にすることが多いようです。
拡張メソッドを使用・宣言する際の注意点
名前空間のインポート
異なる名前空間の型で宣言されている拡張メソッドを使用する場合は、その名前空間をインポートしておく必要があります。 クラス・構造体など他の型と同様、インポートされていない名前空間で宣言されている拡張メソッドを参照しようとしても、名前を解決できずコンパイルエラーとなります。
null変数からの呼び出し
拡張メソッドはnull
/Nothing
が代入されている変数からも呼び出すことができます。 見かけ上はヌル参照となるように見えますが、実際のところこれは引数にnull
/Nothing
を指定して静的メソッドを呼び出すことと変わらないため、実行時エラーとはなりません。
非公開メンバの参照
見かけ上、拡張メソッドはインスタンスメソッドですが、実体はあくまで別のクラスで宣言された静的メソッドであるため、private
やprotected
など外部に公開されていないインスタンスメンバを拡張メソッド内から参照したり呼び出したりすることはできません。 通常のメソッドの場合と同様、拡張メソッド内でアクセスできるのは外部に公開されているメンバのみです。
同一シグネチャのインスタンスメソッドの呼び出し
拡張メソッドと同名で同じシグネチャ(引数リスト)のインスタンスメソッドがすでに型に存在している場合は、型に存在するメソッドの呼び出しが優先されます。 同名のメソッドが存在する場合でも、拡張メソッドとしてではなく静的メソッドとして呼び出すことはできます。