msdn/books で、既存のクラスのソース コードが利用できない場合に既存のクラスにメソッドを追加するのに拡張メソッドが役立つことを読みましたが、いくつかの非常によく書かれたオープン ソース コードで、拡張メソッドがまだ継承と共に使用されていることに気付きました。 (abstract, interface) 作者自身が書いたソースコードを持つクラス。
これは単なる一般的な質問であり、ソース コードはありません。
msdn/books で、既存のクラスのソース コードが利用できない場合に既存のクラスにメソッドを追加するのに拡張メソッドが役立つことを読みましたが、いくつかの非常によく書かれたオープン ソース コードで、拡張メソッドがまだ継承と共に使用されていることに気付きました。 (abstract, interface) 作者自身が書いたソースコードを持つクラス。
これは単なる一般的な質問であり、ソース コードはありません。
一般的な理由は依存関係の管理です。たとえば、かなり一般的な classUser
とそれほど一般的ではないclass があるとしGravatarImage
ます。SomeUser.GravatarImage()
の代わりにを呼び出すことができるのは理にかなっているかもしれませんGravatarImage.ImageForUser(SomeUser)
。これは便利なだけではありません。大規模なプロジェクトでは、他のプログラマーが何かを行うための「正しい」方法を見つけるのが難しい場合があります。ここでは、IntelliSense が大いに役立ちます。
ただし、User クラスは「バックエンド」クラスであり、画像、ビュー、gravatar、または URL について何も知る必要がないため、依存関係をクリーンに保ちたいと考えています。
基本的に拡張メソッドで構成される LINQ にも同様の議論が当てはまります。これらの拡張メソッドは、コレクション インターフェイスを拡張するため、非常に小さなインターフェイスで多くの機能を作成できます。新しい種類の を実装するのIEnumerable
は非常に簡単ですが、LINQ が提供するすべての機能を利用できます。
例に固執するためのIEnumerable
インターフェイスは、列挙子を取得すること以上のものを許可しません。各実装者にメソッドを提供するよう依頼する代わりにCount
、同じことを行う拡張メソッドを呼び出すことができます。
ただし、メソッド likeIEnumerable.Count()
は非常に遅くなる可能性があることに注意してください (すべての要素にアクセスする必要があります)。一方、基になるクラスの直接実装は、単純な int を返すのと同じくらい簡単です。
すでにここにある多くの回答は素晴らしいものです。オプションの動作やインターフェイスで拡張機能を使用するのが大好きです。他にもいくつかの正当な理由があります。
抽象メソッドのオーバーロードを避ける。次のインターフェースを検討してください。
public interface IUserService
{
User GetUser(int userId);
User GetUser(int userId, bool includeProfilePic);
}
IUserService
. _ しかし、インターフェイスに両方のメソッドを使用すると、まったく異なる方法で実装できます (この単純なものはおそらくそうではありませんが、私はこの問題に何度も遭遇します)。拡張メソッドを使用すると、オーバーロードは発散動作を持つことができません。
public interface IUserService
{
User GetUser(int userId, bool includeProfilePic);
}
public static class UserServiceExtensions
{
public static User GetUser(this IUserService userService, int userId)
{
return userService.GetUser(userId, false);
}
}
カプセル化を尊重します。 クラスに追加したい機能が少しあるが、機能するためにクラスの内部メンバーにアクセスする必要がなく、状態を保持しない場合は、拡張メソッドを使用することをお勧めします。クラスの内部メンバーと状態について知っていることが少ないほど、結合が少なくなり、長期的にはコードの保守が容易になります。
欠点: 拡張メソッドを Moq できない 多くの場合、これは問題ではありません。つまり、拡張メソッドの背後にある動作をモック化するには、通常、拡張メソッドがどのように機能するかを理解し、それが呼び出す仮想メソッドをモック化する必要があります。これにより、テストが拡張メソッドの実装に結合されます。拡張メソッドが単純で変更される可能性が低い場合、これは単に面倒です。拡張メソッドが複雑な一連の呼び出しをカプセル化している場合、これはかなり悪いことです。そのため、私は通常、比較的単純な動作の拡張メソッドのみを使用します。
C# では、インターフェースに拡張メソッドを提供することは、通常、* mixinsを近似しようとする試みです。
ミックスインは、メソッドが実装されたインターフェイスと見なすこともできます。
C# は mixin をサポートしていませんが、他の人が実装できるインターフェイス クラスに拡張メソッドを提供することは、機能を "後付け" する良い方法です。
これは、ミックスインとして提供されるボルトオン機能を備えたシンプルなインターフェースの実世界の例です。
public interface IRandomNumberGenerator
{
Int32 NextInt();
}
public static class RandomNumberGeneratorExtensions
{
public static Double NextDouble(this IRandomNumberGenerator instance)
{
return (Double)Int32.MaxValue / (Double)instance.NextInt();
}
}
// Now any class which implements IRandomNumberGenerator will get the NextDouble() method for free...
* C# のミックスインの近似と実際のものとの大きな違いは、サポートされている言語ではミックスインにプライベートな状態を含めることができるのに対し、C# のインターフェイスの拡張メソッドは明らかにパブリックな状態にしかアクセスできないことです。
拡張メソッドの代替としてのサブクラス化には、いくつかの欠点が考えられます。
読みやすさを向上させ、関心の分離を維持する方法で名前空間間の境界を橋渡しするために、拡張メソッドをよく使用します。たとえば、myObject.GetDatabaseEntity()
非常にうまく読み取れますが、GetDatabaseEntity()
のコードは、ビジネス ロジックではなく、コードのデータベース セクションにある必要があります。このコードを拡張メソッドに入れることで、サブクラス化の複雑さを追加することなく、それが属する場所にすべてを保持できます。
さらに、myObject
データベース コードに渡される前にビジネス ロジックでインスタンス化された場合、ビジネス ロジックにデータベースの名前空間を含める必要があります。私は、各モジュールの責任を明確に区分することを好み、ビジネス ロジックがデータベースについてできるだけ知らないことを好みます。
拡張メソッドが役立ついくつかのトリックもあります (そのうちのいくつかは他の回答で既に言及されています)。
拡張メソッドを考える良い方法は、プラグイン型アーキテクチャのようなものです。特定の型インスタンスの機能を含めたり除外したりできます。あなたの質問に具体的に答えるには:
継承ではなくソース コードが利用可能な場合に拡張メソッドを使用する理由
それに対する最も簡単な答えは、オプションの機能です。拡張メソッドを使用する最も一般的で、おそらく最も重要な理由は、コア機能を変更せずに特定の型を拡張できることです。いくつかのメソッドを追加するために新しい型を派生させるのはやり過ぎです。ソースを制御できたとしても、代わりに型を作成する方が理にかなっています。partial
拡張メソッドは、特定のシナリオの問題を解決するために使用される傾向があり、コア コード ベースに入る価値はありません。ただし、それらをあちこちで使用していることに気付いた場合、それはおそらくそれらを正しく使用していないことを示す良い指標です.
たとえば、次の拡張機能を考えてみましょう。
var epochTime = DateTime.UtcNow.ToEpochTime();
ToEpochTime
日時をUnix Timeとして返します。これは、タイムスタンプを生成したり、日付をシリアル化したりする別の方法として役立ちます。ただし、これは非常に特殊な関数であるため、その一部であることは意味がありませんがDateTime
、拡張メソッドにすることで、必要に応じてこのタイプの機能を簡単に含めることができます。
これはやや特殊なケースかもしれませんが、さまざまなタイプの「ルール」を既存のクラスに提供する場合に、拡張メソッドが役立つことがわかりました。
多くのメンバーとデータを持つ classe がありSomeDataContainer
、場合によってはそのデータの特定の部分をエクスポートし、別の場合には他の部分をエクスポートしたいとします。
で次のようなことができますSomeDataContainer
:
if(ShouldIncludeDataXyz()){
exportXyz();
}
(...)
private bool ShouldIncludeDataXyz(){
// rules here
}
private void ExportXyz(){ (...) }
...しかし、特に多くのクラスや多くのルールがある場合など、これが時々面倒になることがわかりました。
場合によっては、「データ クラス」ごとに 1 つの「ルール クラス」を使用してルールを個別のクラスに配置し、ルールを拡張クラスとして作成しました。
これにより、コア データから分離された 1 つの場所でルールの階層が得られます。分離はとにかく便利です。
結果のコードは上記のようになります。
// This now calls an extention method, which can be found
// in eg. "SomeDataContainerRules.cs", along with other similar
// "rules"-classes:
if(this.ShouldIncludeDataXyz()){
exportXyz();
}