C#の拡張メソッド
//Sample1 using System; using Utility; namespace Utility { static class ConsoleUtility { public static void Print( this int val ) { Console.WriteLine( val.ToString() ); } } } class Program { static void Main( string[ ] args ) { int i = 100; i.Print(); } }
サンプルプログラム「Sample1」を実行すると、コンソール画面に100が表示されます。int型はPrintメソッドを持っていないところが、拡張メソッドを理解するポイントです。
Printメソッドの持ち主はint型ではなく、ConsoleUtilityクラスです。それにも関わらず、int型のインスタンスはPrintメソッドを呼び出せます。これが拡張メソッドです。拡張メソッドを使用する事により、既存の型の機能を拡張できます。
拡張メソッドを宣言する方法は簡単です。静的クラス内で、パラメータにthisキーワードをつけた静的メソッドを宣言するだけです。使用するのも簡単で、拡張メソッドが定義された静的クラスを参照するだけです。以上で、拡張メソッドの概要が分かったと思いますので、詳細な説明に入ります。
Printメソッドの引数の型がint型になっているので、int型でPrintメソッドを呼び出す事になります。従って、次のプログラムはコンパイルエラーになります。
long m = 100; m.Print(); byte b = 100; b.Print()();
コンパイルエラーを一見すると、暗黙の型変換が出来る場合でもエラーを出しているので、拡張メソッドは厳密に型を判定しているように見えます。しかし、拡張メソッドは、そこまで厳密に型を判定しません。子クラスが、拡張メソッドを使用する事を許します。これは一般的には望ましい事ですが、良く考えないで拡張メソッドを使用すると、この動作がもとでバグを生む可能性があります。
//Sample2 using System; using Utility; namespace Utility { static class ConsoleUtility { public static void Print( this Parent obj ) { obj.Method(); } } } class Parent { public virtual void Method() { Console.WriteLine( "Parentクラスのメソッド" ); } } class Child : Parent { public new void Method() { Console.WriteLine( "Childクラスのメソッド" ); } } class Program { static void Main( string[ ] args ) { Child obj = new Child(); obj.Print(); } }
Sample2を実行すると、「Childクラスのメソッド」と表示されると思った人も多いでしょう。しかし、実際は「Parentクラスのメソッド」と表示されます。理由は、Childクラスがメソッドを隠蔽しているからです。このサンプルプログラムは短いので、そんな事をしないと考える人がいるかもしれません。しかし、拡張メソッドの内容が複雑で、大量のプログラム内で仮想メソッドを呼び出している場合、このバグの発見は難しくなります。また、拡張メソッドが実装された後で、新しい子クラスが仮想メソッドを隠蔽するかもしれません。従って、拡張メソッド内で仮想メソッドを呼び出さないのが得策だと言えます。
拡張メソッドは、ただ便利だからという理由で、気軽に使ってはなりません。実務で使用する際には、よく検討する必要があります。