拡張メソッド (C# プログラミング ガイド)
更新 : 2007 年 11 月
拡張メソッドを使用すると、新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく既存の型にメソッドを "追加" できます。拡張メソッドは特別な種類の静的メソッドですが、拡張された型のインスタンス メソッドのように呼び出します。C# および Visual Basic で作成されたクライアント コードの場合は、拡張メソッドの呼び出しと、型で実際に定義されたメソッドの呼び出しに明確な違いはありません。
最も一般的な拡張メソッドは、既存の System.Collections.IEnumerable 型と System.Collections.Generic.IEnumerable<T> 型にクエリ機能を追加する LINQ 標準クエリ演算子です。標準クエリ演算子を使用するには、最初に using System.Linq ディレクティブを使用して、これらの演算子をスコープに取り込みます。IEnumerable<T> を実装するすべての型は、GroupBy、OrderBy、Average などのインスタンス メソッドを持っていると考えられます。List<T>、Array などの IEnumerable<T> 型のインスタンスの後に "ドット" を入力すると、IntelliSense により、ステートメントの入力候補としてこれらの追加メソッドが表示されます。
整数の配列において、標準クエリ演算子の OrderBy メソッドを呼び出す方法を次の例に示します。かっこ内の式はラムダ式です。標準クエリ演算子の多くはパラメータとしてラムダ式を受け取りますが、拡張メソッドでは、これは必須ではありません。詳細については、「ラムダ式 (C# プログラミング ガイド)」を参照してください。
class ExtensionMethods2
{
static void Main()
{
int[] ints = { 10, 45, 15, 39, 21, 26 };
var result = ints.OrderBy(g => g);
foreach (var i in result)
{
System.Console.Write(i + " ");
}
}
}
//Output: 10 15 21 26 39 45
拡張メソッドは、静的メソッドとして定義しますが、インスタンス メソッドの構文を使用して呼び出します。最初のパラメータでは、メソッドが操作する型を指定します。このパラメータの前には this 修飾子を付加します。using ディレクティブを使用して名前空間をソース コードに明示的にインポートした場合、拡張メソッドはそのスコープでのみ有効です。
System.String クラスに対して拡張メソッドを定義する例を次に示します。入れ子になっていない、非ジェネリックの静的クラス内で定義されていることに注意してください。
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
この using ディレクティブを使用することで、WordCount 拡張メソッドをスコープに取り込むことができます。
using ExtensionMethods;
また、この構文を使用することで、アプリケーションから呼び出すことができます。
string s = "Hello Extension Methods";
int i = s.WordCount();
コードでは、インスタンス メソッドの構文を使用して拡張メソッドを呼び出します。ただし、コンパイラが生成する中間言語 (IL: Intermediate Language) により、コードは静的メソッドに対する呼び出しに変換されます。したがって、カプセル化の原則には実質的に違反していません。実際に、拡張メソッドは、それらが拡張している型のプライベート変数にはアクセスできません。
詳細については、「方法 : カスタム拡張メソッドを実装して呼び出す (C# プログラミング ガイド)」を参照してください。
一般的には、独自の拡張メソッドを実装するよりも、拡張メソッドを呼び出すことの方がはるかに多くなります。拡張メソッドは、インスタンス メソッドの構文を使用して呼び出すので、特別な知識がなくてもクライアント コードからそれらを使用できます。メソッドが定義されている名前空間に関する using ディレクティブを追加するだけで、特定の型の拡張メソッドを使用できるようになります。たとえば、標準クエリ演算子を使用するには、次の using ディレクティブをコードに追加します。
using System.Linq;
場合によっては、System.Core.dll への参照も追加する必要があります。ほとんどの IEnumerable<T> 型で利用できる追加メソッドとして、標準クエリ演算子が IntelliSense により表示されるようになりました。
メモ : |
---|
String の場合、IntelliSense により標準クエリ演算子は表示されませんが、利用できます。 |
コンパイル時の拡張メソッドのバインディング
拡張メソッドを使用してクラスまたはインターフェイスを拡張することはできますが、これらをオーバーライドすることはできません。インターフェイス メソッドまたはクラス メソッドと同じ名前およびシグネチャを持つ拡張メソッドは決して呼び出されません。コンパイル時に、型自体で定義されているインスタンス メソッドよりも低い優先順位が拡張メソッドには必ず設定されます。つまり、型に Process(int i) という名前のメソッドがあり、これと同じシグネチャの拡張メソッドがある場合、コンパイラは必ずインスタンス メソッドにバインドします。コンパイラは、メソッド呼び出しを検出すると、最初に型のインスタンス メソッドから一致するものを探します。一致するものが見つからない場合、型に対して定義されている拡張メソッドを検索し、見つかった最初の拡張メソッドにバインドします。次の例は、コンパイラが拡張メソッドとインスタンス メソッドのどちらにバインドするかを決定する方法を示しています。
使用例
次の例は、C# のコンパイラがメソッド呼び出しを型のインスタンス メソッドにバインドするか、拡張メソッドにバインドするかを決定するときに従う規則を示しています。Extensions 静的クラスには、IMyInterface を実装する型に対して定義された拡張メソッドが含まれています。A、B、および C の各クラスはすべてこのインターフェイスを実装しています。
MethodB メソッドは、その名前とシグネチャがクラスにより既に実装されているメソッドと完全に一致しているため、呼び出されません。
コンパイラは、一致するシグネチャを持つインスタンス メソッドを検出できない場合、一致する拡張メソッド (存在する場合) にバインドします。
namespace Extensions
{
using System;
using ExtensionMethodsDemo1;
// Define extension methods for any type that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i)
{
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
}
public static void MethodA(this IMyInterface myInterface, string s)
{
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
}
// This method is never called, because the three classes implement MethodB.
public static void MethodB(this IMyInterface myInterface)
{
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
}
namespace ExtensionMethodsDemo1
{
using System;
using Extensions;
public interface IMyInterface
{
void MethodB();
}
class A : IMyInterface
{
public void MethodB(){Console.WriteLine("A.MethodB()");}
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj) { Console.WriteLine("C.MethodA(object obj)"); }
}
class ExtMethodDemo
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
C c = new C();
TestMethodBinding(a,b,c);
}
static void TestMethodBinding(A a, B b, C c)
{
// A has no methods, so each call resolves to
// the extension methods whose signatures match.
a.MethodA(1); // Extension.MethodA(object, int)
a.MethodA("hello"); // Extension.MethodA(object, string)
a.MethodB(); // A.MethodB()
// B itself has a method with this signature.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method, but Extension does.
b.MethodA("hello"); // Extension.MethodA(object, string)
// In each case C has a matching instance method.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
}
}
}
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
一般的なガイドライン
拡張メソッドは、一般的に、必要な場合に限り注意して実装することをお勧めします。クライアント コードで既存の型を拡張する必要がある場合、可能であれば既存の型から派生した新しい型を作成することで行ってください。詳細については、「継承 (C# プログラミング ガイド)」を参照してください。
拡張メソッドを使用して、変更できないソース コードのある型を拡張する場合、型の実装の変更により拡張メソッドが破損するというリスクを負うことになります。
所定の型の拡張メソッドを実装する場合、次の 2 つの点に注意してください。
拡張メソッドが型で定義されているメソッドと同じシグネチャを持つ場合、その拡張メソッドは呼び出されません。
拡張メソッドは名前空間レベルでスコープ内に取り込まれます。たとえば、Extensions という名前の単一の名前空間に、拡張メソッドを含む複数の静的クラスがある場合、using Extensions; ディレクティブによって、それらのすべての拡張メソッドがスコープ内に取り込まれます。
クラス ライブラリの実装では、アセンブリの新しいバージョンを作成するのを回避するために、拡張メソッドは使用しないでください。重要な新しい機能をライブラリに追加する必要があり、ソース コードを所有している場合は、アセンブリのバージョン管理に関する .NET Framework の標準ガイドラインに従ってください。詳細については、「アセンブリのバージョン管理」を参照してください。
参照
概念
参照
その他の技術情報
Conversion rules for Instance parameters and their impact