web-dev-qa-db-ja.com

拡張メソッドの利点は何ですか?

C#の「非信者」は、拡張メソッドの目的は何かを尋ねてきました。特に、元のオブジェクトのソースを所有/制御していない場合、既に定義されているオブジェクトに新しいメソッドを追加できることを説明しました。

彼は「なぜ自分のクラスにメソッドを追加しないのですか?」私たちは(良い意味で)くるくる回っています。私の一般的な応答は、それがツールベルトの別のツールであり、彼の応答はツールの無駄な無駄遣いであるということです...

独自のクラスに追加されたメソッドを使用することはできなかった(または使用すべきでない)拡張メソッドを使用したシナリオは何ですか?

83
Jerry

拡張メソッドは、コードを記述するときに非常に役立つと思います。拡張メソッドを基本型に追加すると、インテリセンスですばやく取得できます。

ファイルサイズのフォーマット のフォーマットプロバイダーがあります。それを使用するには、次のように書く必要があります。

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));

私が書くことができる拡張メソッドの作成:

Console.WriteLine(fileSize.ToFileSize());

よりクリーンでシンプル。

33

のみ拡張メソッドの利点は、コードが読みやすいことです。それでおしまい。

拡張メソッドにより、これを行うことができます。

foo.bar();

これの代わりに:

Util.bar(foo);

C#には、このようなものがたくさんあります。言い換えれば、C#には多くの機能があり、それらは些細なものであり、それ自体には大きな利点はありません。ただし、これらの機能を組み合わせると、その部分の合計よりも少し大きいものが見え始めます。 LINQクエリは、それなしではほとんど読めないので、拡張メソッドから大きな恩恵を受けます。 LINQは拡張メソッドなしではpossibleになりますが、実用的ではありません。

拡張メソッドは、C#の部分クラスによく似ています。それ自体はあまり役に立たず、ささいなように見えます。ただし、生成されたコードを必要とするクラスで作業を開始すると、部分クラスがより意味を持ち始めます。

83
Andrew Hare

ツールを忘れないでください!タイプFooに拡張メソッドMを追加すると、Fooのインテリセンスリストに「M」が表示されます(拡張クラスがスコープ内にあると想定)。これにより、MyClass.M(Foo、...)よりも「M」を見つけるのがはるかに簡単になります。

結局のところ、それは別の場所で静的な方法のための単なる構文上の砂糖ですが、家を買うようなものです:「場所、場所、場所!」それが型にかかっている場合、人々はそれを見つけるでしょう!

33
Brian

私が出会った拡張方法のもう2つの利点:

  • fluentインターフェイスは拡張メソッドの静的クラスにカプセル化できます。これにより、コアクラスとその流れる拡張機能の間の懸念を分離できます。メンテナンス性が向上するのを見てきました
  • 拡張メソッドをインターフェイスから切り離すことができるため、コントラクト(インターフェイスを介して)および関連する一連のインターフェイスベースの動作(拡張メソッドを介して)を指定できるようになり、懸念の分離を提供します。
29
David Alpert

拡張メソッドの私が持っていた最良の使用法のいくつかは以下の機能です:

  1. 多くの場合、sealedのマークが付けられるサードパーティのオブジェクトの機能を拡張します(商用または社内の別のグループによって管理されます)。
  2. 抽象クラスを実装せずにインターフェイスのデフォルト機能を作成します

たとえば、IEnumerable<T>。拡張メソッドが豊富ですが、一般的なForEachメソッドを実装していないのは面倒です。だから、私は自分で作った:

public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
    foreach ( var o in enumerable )
    {
        action(o);
    }
}

出来上がり、私のすべてのIEnumerable<T>オブジェクトは、型の実装に関係なく、コードを記述したかどうかに関係なく、コードに適切な「using」ステートメントを追加することでForEachメソッドを持っています。

26
Jon Limjap

拡張メソッドを使用する大きな理由の1つはLINQです。拡張メソッドがなければ、LINQでできることの多くは非常に困難です。 Where()、Contains()、Select拡張メソッドは、構造を変更せずに既存の型にさらに多くの機能を追加することを意味します。

12
Ray Booysen

拡張メソッドのadvantagesについては多くの回答があります。 短所に対処する方法はありますか?

最大の欠点は、同じコンテキストで同じシグネチャを持つ通常のメソッドと拡張メソッドがある場合、コンパイラのエラーや警告がないことです。

特定のクラスに適用する拡張メソッドを作成するとします。その後、そのクラス自体に同一の署名を持つメソッドを誰かが作成します。

コードがコンパイルされ、実行時エラーが発生することさえありません。 ただし、以前と同じコードを実行しなくなっています。

9
Ryan Lundy

Fluent Interfaces and Context SensitivityGreg Youngが CodeBetter で示したとおり

8
cgreeno

拡張メソッドに対する私の個人的な主張は、それらがOOP設計に非常によく適合する:単純なメソッドを検討することです

bool empty = String.IsNullOrEmpty (myString)

と比較して

bool empty = myString.IsNullOrEmpty ();
4
Bluenuance

ここでは、拡張メソッドの背後にある重要な理由としてコードの可読性の向上に言及している他の回答をサポートしたいと思います。これについては、メソッドチェーンとネストされたメソッド呼び出しの2つの側面、および無意味な静的クラス名でのLINQクエリの乱雑さでこれを示します。


このLINQクエリを例として見てみましょう。

numbers.Where(x => x > 0).Select(x => -x)

WhereSelectはどちらも拡張メソッドであり、静的クラスEnumerableで定義されています。したがって、拡張メソッドが存在せず、これらが通常の静的メソッドである場合、コードの最終行は基本的に次のようになります。

Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)

クエリがどれだけ厄介かを確認します。


第二に、独自のクエリ演算子を導入したい場合、他のすべての標準クエリ演算子と同様に、Enumerableはフレームワーク内にあるため、Enumerable静的クラス内で定義する方法は当然ありません。そのクラスを制御することはできません。したがって、拡張メソッドを含む独自の静的クラスを定義する必要があります。その後、次のようなクエリを取得できます。

Enumerable.Select(MyEnumerableExtensions.RemoveNegativeNumbers(numbers), x => -x)
//                ^^^^^^^^^^^^^^^^^^^^^^
//                different class name that has zero informational value
//                and, as with 'Enumerable.xxxxxx', only obstructs the
//                query's actual meaning.
4
stakx

上記の拡張方法で何ができるかについては、上記の素晴らしい回答が山ほどあります。

私の短い答えは-工場の必要性をほぼ排除します。

それらは新しい概念ではなく、Objective-Cのキラー機能(categories)であるという最大の検証の1つです。彼らは、フレームワークベースの開発に非常に多くの柔軟性を追加し、NeXTはNSAとウォール街の金融モデラーを主要ユーザーとしていた。

REALbasicは、それらをextends methodsとして実装し、同様の用途で開発を簡素化しました。

4
Andy Dent

拡張メソッドは、クラスとクラスの依存関係をクリーンに保つのにも役立ちます。たとえば、Fooが使用されるすべての場所でFooクラスのBar()メソッドが必要になる場合があります。ただし、別のアセンブリに.ToXml()メソッドが必要な場合がありますが、そのアセンブリ専用です。その場合、必要なSystem.Xmlおよび/またはSystem.Xml.Linqの依存関係を、元のアセンブリではなく、そのアセンブリに追加できます。

利点:定義クラスAssembly内の依存関係は、最低限必要なものだけに削減され、他の消費アセンブリはToXml()メソッドを使用できなくなります。詳細については、 this PDC presentation を参照してください。

3
user29439

(拡張)メソッドをクラスに直接追加できることは事実です。しかし、すべてのクラスがあなたによって書かれているわけではありません。コアライブラリまたはサードパーティのライブラリのクラスは閉じられていることが多く、拡張メソッドなしで合成シュガーを取得することは不可能です。ただし、拡張メソッドは、たとえば、 C++

3
Schildmeijer

拡張メソッドは、実際には.NETに組み込まれている "Introduce Foreign Method" Martin Fowler's Bookのリファクタリング(メソッドシグネチャまで)です。基本的に同じ利点と落とし穴があります。このリファクタリングのセクションで、彼は、メソッドを実際に所有するクラスを変更できない場合の回避策であると述べています。

2
Jason Punyon

拡張メソッドはコードの可読性を向上させることに同意しますが、それは実際には静的ヘルパーメソッドに他なりません。

クラスに動作を追加するために拡張メソッドを使用するIMOは次のとおりです。

紛らわしい:プログラマーは、メソッドが拡張型の一部であると信じている可能性があり、そのため、拡張ネームスペースがインポートされない場合にメソッドが消える理由を理解できません。

アンチパターン:拡張メソッドを使用してフレームワークの型に動作を追加し、ユニットテストに参加する人にそれらを出荷することにしました。現在、彼は偽造できないメソッドの束を含むフレームワークに固執しています。

2
HaraldV

私は主に拡張メソッドを、おそらくそれらが無料の機能を許可してはならないことを認めていると考えています。

C++コミュニティでは、メンバーよりも無料の非メンバー関数を優先するのが良いOOPプラクティスと見なされることがよくあります。拡張メソッドは、同じことを達成するための回り道のようです。つまり、プライベートメンバーにアクセスできない静的関数のよりクリーンな構文です。

拡張メソッドは構文糖にすぎませんが、それらを使用しても害はありません。

2
jalf

それらを使用して、オブジェクトモデルクラスを再利用します。データベースにあるオブジェクトを表すクラスがたくさんあります。これらのクラスは、基本的な使用法がプロパティにアクセスするようにオブジェクトを表示するためにのみクライアント側で使用されます。

public class Stock {
   public Code { get; private set; }
   public Name { get; private set; }
}

その使用パターンのために、これらのクラスにビジネスロジックメソッドを入れたくないので、すべてのビジネスロジックを拡張メソッドにします。

public static class StockExtender {
    public static List <Quote> GetQuotesByDate(this Stock s, DateTime date)
    {...}
}

このようにして、不要なコードでクライアント側をオーバーロードすることなく、ビジネスロジック処理とユーザーインターフェイス表示に同じクラスを使用できます。

このソリューションの興味深い点の1つは、オブジェクトモデルクラスが Mono.Cecil を使用して動的に生成されることです。したがって、必要な場合でもビジネスロジックメソッドを追加することは非常に困難です。 XML定義ファイルを読み取り、データベースにあるオブジェクトを表すこれらのスタブクラスを生成するコンパイラがあります。この場合の唯一のアプローチは、それらを拡張することです。

2
Edwin Jarvis
  • Itselfいユーティリティ関数を呼び出す代わりに、オブジェクト自体のIntelliSense
  • 変換関数の場合、「XToY(X x)」を「ToY(this X x)」に変更すると、いXToY(x)の代わりにpretty x.ToY()になります。
  • 制御できないクラスを拡張する
  • クラス自体にメソッドを追加することが望ましくない場合は、クラスの機能を拡張します。たとえば、ビジネスオブジェクトをシンプルかつロジックフリーに保ち、拡張メソッドにspecificい依存関係を持つ特定のビジネスロジックを追加できます
2
davogones

これにより、C#は動的言語、LINQ、その他多数のサポートを強化できます。 Scott Guthrieの記事 をご覧ください。

1
DavGarcia

拡張メソッドが非常に有用なケースの1つは、ASMX Webサービスを使用するクライアントアプリケーションです。シリアル化のため、Webメソッドの戻り値の型にはメソッドが含まれません(これらの型のパブリックプロパティのみがクライアントで利用可能です)。

拡張メソッドを使用すると、クライアント側でさらに別のオブジェクトモデルや多数のラッパークラスを作成することなく、Webメソッドによって返される型に(クライアント側で)機能を追加できます。

1
M4N

前回のプロジェクトでは、拡張メソッドを使用してValidate()メソッドをビジネスオブジェクトにアタッチしました。シリアル化可能なデータ転送オブジェクトのビジネスオブジェクトは、製品、顧客、商人などの一般的なeコマースエンティティのように異なるドメインで使用されるため、これを正当化しました。データ転送オブジェクトの基本クラスに関連付けられたValidateメソッドの遅延バインド検証ロジック。これが理にかなっていることを望みます:)

1
Athens

また、拡張メソッドは、C#スタイルで使用した場合にLinqクエリを読みやすくする方法として追加されたことを思い出してください。

これら2つの影響は完全に同等ですが、最初の影響ははるかに読みやすくなっています(もちろん、メソッドの連鎖が増えると読みやすさのギャップが大きくなります)。

int n1 = new List<int> {1,2,3}.Where(i => i % 2 != 0).Last();

int n2 = Enumerable.Last(Enumerable.Where(new List<int> {1,2,3}, i => i % 2 != 0));

完全修飾構文は次のようになっている必要があることに注意してください。

int n1 = new List<int> {1,2,3}.Where<int>(i => i % 2 != 0).Last<int>();

int n2 = Enumerable.Last<int>(Enumerable.Where<int>(new List<int> {1,2,3}, i => i % 2 != 0));

偶然、WhereおよびLastの型パラメーターは、これらの2つのメソッドの最初のパラメーター(キーワードthisによって導入され、拡張メソッドにすることで推測できるため、明示的に言及する必要はありません。 )。

この点は、明らかに拡張メソッドの(とりわけ)利点であり、メソッドチェーンが関係するすべての同様のシナリオでこの利点を活用できます。

特に、サブクラスから呼び出し可能な基本クラスメソッドを持ち、このサブクラス(サブクラスタイプ)への厳密に型指定された参照を返すことがわかった、よりエレガントで説得力のある方法です。

例(わかりました、このシナリオは完全に安っぽいです):おやすみの後、動物は目を開けて泣きます。すべての動物は同じように目を開きますが、犬は鳴き声を上げ、アヒルは鳴きます。

public abstract class Animal
{
    //some code common to all animals
}

public static class AnimalExtension
{
    public static TAnimal OpenTheEyes<TAnimal>(this TAnimal animal) where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return animal; //returning a self reference to allow method chaining
    }
}

public class Dog : Animal
{
    public void Bark() { /* ... */ }
}

public class Duck : Animal
{
    public void Kwak() { /* ... */ }
}

class Program
{
    static void Main(string[] args)
    {
        Dog Goofy = new Dog();
        Duck Donald = new Duck();
        Goofy.OpenTheEyes().Bark(); //*1
        Donald.OpenTheEyes().Kwak(); //*2
    }
}

概念的にはOpenTheEyesAnimalメソッドである必要がありますが、それはAnimalBarkなどの特定のサブクラスメソッドを知らない抽象クラスDuckのインスタンスを返します。 * 1および* 2としてコメントされた2行は、コンパイルエラーを発生させます。

しかし、拡張メソッドのおかげで、一種の「呼び出されるサブクラスのタイプを知っている基本メソッド」を持つことができます。

単純なジェネリックメソッドで仕事をすることもできますが、はるかに厄介なことに注意してください。

public abstract class Animal
{
    //some code common to all animals

    public TAnimal OpenTheEyes<TAnimal>() where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return (TAnimal)this; //returning a self reference to allow method chaining
    }
}

今回はパラメーターがないため、戻り値の型の推論はありません。呼び出しは、次のもの以外にできません。

Goofy.OpenTheEyes<Dog>().Bark();
Donald.OpenTheEyes<Duck>().Kwak();

...より多くのチェーンが必要な場合は、コードの負荷が大きくなります(特に、型パラメーターが常に<Dog> Goofyの行と<Duck>ドナルドのものに...)

1
Ssithra

拡張メソッドは、ネストされたジェネリック引数と一致するのに役立つことがわかりました。

少し奇妙に聞こえますが、ジェネリッククラスMyGenericClass<TList>、およびTList自体がジェネリックであることを知っています(例:List<T>)、拡張メソッドまたは静的ヘルパーメソッドを使用せずに、ネストされた「T」をリストから掘り下げる方法はないと思います。静的なヘルパーメソッドのみを自由に使用できる場合、(a)andく、(b)クラスに属する機能を外部の場所に強制的に移動します。

例えばタプルの型を取得してメソッドシグネチャに変換するには、拡張メソッドを使用できます。

public class Tuple { }
public class Tuple<T0> : Tuple { }
public class Tuple<T0, T1> : Tuple<T0> { }

public class Caller<TTuple> where TTuple : Tuple { /* ... */ }

public static class CallerExtensions
{
     public static void Call<T0>(this Caller<Tuple<T0>> caller, T0 p0) { /* ... */ }

     public static void Call<T0, T1>(this Caller<Tuple<T0, T1>> caller, T0 p0, T1 p1) { /* ... */ }
}

new Caller<Tuple<int>>().Call(10);
new Caller<Tuple<string, int>>().Call("Hello", 10);

そうは言っても、分割線がどこにあるべきかはわかりません-いつメソッドを拡張メソッドにすべきか、そしていつ静的ヘルパーメソッドにすべきか?何かご意見は?

0
Andy

拡張メソッドは、より明確なコードを書くのに役立つと思います。

友達が提案したように、クラス内に新しいメソッドを配置する代わりに、ExtensionMethods名前空間に配置します。このようにして、クラスの論理的な秩序を維持します。クラスを実際に直接処理しないメソッドは、クラスを混乱させません。

拡張メソッドを使用すると、コードがより明確になり、より適切に整理されます。

0
Alex Baranosky

Htmlを構築するのに気に入っています。多くの場合、繰り返し使用されるセクション、または関数が有用であるが再帰的に生成されるセクションがありますが、そうでない場合はプログラムのフローが中断されます。

        HTML_Out.Append("<ul>");
        foreach (var i in items)
            if (i.Description != "")
            {
                HTML_Out.Append("<li>")
                    .AppendAnchor(new string[]{ urlRoot, i.Description_Norm }, i.Description)
                    .Append("<div>")
                    .AppendImage(iconDir, i.Icon, i.Description)
                    .Append(i.Categories.ToHTML(i.Description_Norm, urlRoot)).Append("</div></li>");
            }

        return HTML_Out.Append("</ul>").ToString();

また、オブジェクトがHTML出力拡張メソッド用にカスタムロジックを準備する必要がある場合、クラス内でプレゼンテーションとロジックを混在させずにこの機能を追加できます。

0
Kelly Gendron

上記のように、特にIEnumerablesには拡張メソッドの非常に多くの素晴らしい例があります。

例えばIEnumerable<myObject>がある場合は、IEnumerable<myObject>の拡張メソッドを作成および拡張できます

mylist List<myObject>;

...リストを作成する

mylist.DisplayInMyWay();

拡張メソッドがなければ、メソッドを呼び出す必要があります:

myDisplayMethod(myOldArray); // can create more nexted brackets.

もう1つの優れた例は、フ​​ラッシュ内で循環リンクリストを作成することです。

私はそれを信用することができます!

拡張メソッドを使用した循環リンクリスト

これらを組み合わせて、拡張メソッドを使用してコードを次のように読み取ります。

myNode.NextOrFirst().DisplayInMyWay();

のではなく

DisplayInMyWay(NextOrFirst(myNode)).

拡張メソッドの使用見やすく、読みやすく、オブジェクト指向です。また非常に近い:

myNode.Next.DoSomething()

それを同僚に見せてください! :)

0
Gregor

拡張メソッドを使用して、C#で mixinの種類 を作成できます。

これにより、直交する概念の懸念事項をより適切に分離できます。例として、この answer を見てください。

これは、C#でrolesを有効にするためにも使用できます。これは、 DCIアーキテクチャ の中核をなす概念です。

0
Jordão

エディター/ IDEがスマートにオートコンプリートの提案を行えるようにします。

0
Dennis C

画面に入力ゾーンがあり、すべてが正確なタイプ(テキストボックス、チェックボックスなど)に関係なく標準動作を実装する必要があります。入力ゾーンの各タイプは特定のクラス(TextInputBoxなど)から既に派生しているため、共通の基本クラスを継承できません。

おそらく、継承階層に上がることで、WebControlなどの共通の祖先を見つけることができましたが、フレームワーククラスWebControlを開発せず、必要なものを公開していません。

拡張メソッドを使用すると、次のことができます。

1)WebControlクラスを拡張してから、すべての入力クラスで統一された標準動作を取得する

2)あるいは、すべてのクラスをIInputZoneなどのインターフェイスから派生させ、このインターフェイスをメソッドで拡張します。これで、すべての入力ゾーンでインターフェースに関連する拡張メソッドを呼び出すことができます。このように、入力ゾーンはすでに複数の基本クラスから派生しているため、一種の多重継承を実現しました。

0
Ssithra

それについて話す言葉は1つだけです:[〜#〜] maintainability [〜#〜]これは拡張メソッド使用のキーです

0
Tamir