ここでは、C#の拡張メソッドについて説明します。拡張メソッドはLINQを理解する上でも非常に重要な概念になります。
拡張メソッドとは?
拡張メソッドとはすでにあるクラスに対して、継承することなくメソッドを追加する仕組みのことで、C# 3.0で導入されました。クラスだけではなくインターフェイスに対しても拡張メソッドを定義することが非常に重要です。拡張メソッドを使えばインターフェイスに対してもメソッドの処理を定義することができ、そのインターフェイスを実装したクラスではその拡張メソッドをインスタンスメソッドのように用いることができるようになります。
ここでは、拡張メソッドの宣言方法と使い方、そして拡張メソッドを用いてできることについて説明していきます。
拡張メソッドの宣言
基本書式
static [戻り値データ型] [拡張メソッド名](this [拡張する型] [引数1], [データ型] [引数2], ... )
{
[処理]
}
コード例
public static class ExtenderMethods
{
//先頭のnumberで指定される文字数だけ削除した文字列を返す
public static string TrimStartNum(this string s, int number)
{
return s.Substring(number);
}
}
拡張メソッドは拡張すべき型を第一引数とした静的メソッドとして静的クラス内に記述します。この時、第一引数にはthisキーワードをつける必要があります。
コード例では、Stringクラスの拡張メソッドを作成しています。この時メソッドの第一引数は「this string s」として、ここでStringクラスの拡張メソッドであることを宣言します。第二引数以降に実際のメソッド実行時に受け取る引数を指定します。コード例では引数で指定した文字数だけ文字列の先頭を削除するメソッドであるので、その文字数を第二引数で指定しています。
それでは次項では、ここで宣言した拡張メソッドを実際に使ってみましょう。
拡張メソッドの使い方
基本書式
[インスタンス].[拡張メソッド名]([引数2], ... );
コード例
class Program
{
static void Main(string[] args)
{
string s = "123456789";
string trimmed_s = s.TrimStartNum(5);
Console.WriteLine(trimmed_s);
}
}
拡張メソッドは、拡張されたクラスのインスタンスメソッドと同様に呼び出すことができます。ただし、拡張メソッドの第一引数には呼び出し元のインスタンスが代入され、メソッド実行時に指定する引数は第二引数以降に代入されます。メソッドがジェネリックで定義されている場合で第一引数で型パラメーターが推定可能な場合は、型パラメーターの< >は省略可能です。
コード例では前項で宣言した拡張メソッドを呼び出していますが、呼び出し元のインスタンスの「s」がTrimStartNumメソッドの第一引数に代入され、削除する文字数を表す「5」が第二引数に代入されています。このように、拡張メソッドを呼び出す際は、実際に指定する引数は第二引数以降になる点に注意が必要です。
また、拡張メソッドはインスタンスメソッドのように呼び出すことができますが、その定義から分かる通りあくまでもクラスの外から定義された静的メソッドであることにも注意してください。
拡張メソッドを用いる意義
拡張メソッドはクラスを継承することなくメソッドを追加することが可能になるので便利な機能ではありますが、しかし、ソースコードの可読性の観点からは素直にクラスを継承してメソッドを追加した方がいいに違いありません。では、拡張メソッドを用いる意義はどこにあるのでしょうか?
インターフェイスにメソッドの定義を書く
通常はインターフェイスはメソッドの引数や戻り値などの外枠だけ定義して、その処理はインターフェイスを実装したクラスで記述することになりますが、インターフェイスに対して拡張メソッドで処理まで記述したメソッドを定義してしまうことが可能になります。そのインターフェイスを実装すれば、自動的にそのクラスからその拡張メソッドも使えるようになります。
静的メソッドをインスタンスメソッドのように記述できるようにする
もう1つの拡張メソッドの重要なポイントは「静的メソッドでありながらインスタンスメソッドのように記述することができる」という点です。一般的にインターフェイスで定義されるメソッドはインスタンスに依存しない静的メソッドになることが多いですが、静的メソッドとインスタンスメソッドでは連続して処理を行う際に次のような違いがあります。
例えば「A」というオブジェクトに対して処理1(Method1)を行い、出力された「B」というオブジェクトに対して処理2(Method2)を行うということを考えてみましょう。インスタンスメソッドであれば、次のように処理を書くことができます。
A.Method1().Method2();
これは、「A」に対して「Method1」を行い「Method2」を行うという直感的なイメージがそのままC#コードにも反映されていて理解しやすいと思います。しかし、静的メソッドではMethod1の引数に「A」を入れる必要があり、Method2の引数に「B」を入れておく必要があります。そのため、この処理は次のようになります。
Method2(Method1(A));
Method1とMethod2の順番が変わっただけで、先ほどとは違って直感的には理解しにくくなってしまいました。ネストも深くなり可読性も低下しています。
しかし、この静的メソッドを拡張メソッドとして宣言すればインスタンスメソッドの場合と同じ順番でC#コードを書くことができるようになります。
以上のことから、特にインターフェイスにメソッドを追加する際に「そのインターフェイスを実装するクラスによらず同じ処理を表すメソッド」「連続して処理を行う必要のあるメソッド」である場合は、拡張メソッドとして記述するメリットがあります。そして、この特徴を最大限活用しているのがLINQです。例えば、以下のようなLINQを用いたコードを考えてみましょう。
data.Where([処理]).Select([処理]);
WhereメソッドもSelectメソッドもIEnumerable<T>インターフェイスの拡張メソッドとしてEnumerableクラスに定義されています。これによりIEnumerable<T>インターフェイスを実装することでこれらのメソッドを使えるようになり、また「data」→「Whereメソッド」→「Selectメソッド」という処理の順番でメソッドを記述することができるようになりました。
コメント