25

.Net 3.5の新しい拡張機能により、機能をインターフェイスから分割できます。

たとえば、.Net2.0では

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }

    List<IChild> GetChildren()
}

(3.5で)次のようになります:

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }
}

public static class HaveChildrenExtension {
    public static List<IChild> GetChildren( this IHaveChildren ) {
        //logic to get children by parent type and id
        //shared for all classes implementing IHaveChildren 
    }
}

これは、多くのインターフェイスにとってより優れたメカニズムのように思えます。このコードを共有するための抽象的なベースは不要になり、機能的にはコードは同じように機能します。これにより、コードの保守が容易になり、テストが容易になります。

唯一の欠点は、抽象ベースの実装が仮想である可能性があることですが、それを回避することはできます(インスタンスメソッドは同じ名前の拡張メソッドを非表示にしますか?そうすることでコードが混乱しますか?)

このパターンを定期的に使用しない他の理由はありますか?


明確化:

ええ、私は拡張メソッドの傾向がどこでもそれらで終わることだと思います。ピアレビューをあまり行わずに.Net値型を使用する場合は特に注意が必要です(文字列にあるのは.SplitToDictionary()-と似て.Split()いますが、Key-Value区切り文字も使用していると思います)

そこにはベストプラクティスの議論があると思います;-)

(ちなみに、DannySmurf、あなたのPMは怖いですね。)

ここでは、以前はインターフェイスメソッドがあった拡張メソッドの使用について具体的に質問しています。


私は多くのレベルの抽象基本クラスを避けようとしています-これらのモデルを実装するクラスはほとんどすでに基本クラスを持っています。このモデルは、オブジェクト階層をさらに追加するよりも保守性が高く、過度に結合されていない可能性があると思います。

これは、MSがLinqのIEnumerableおよびIQueryableに対して行ったことですか?

4

11 に答える 11

10

拡張メソッドは、拡張機能として使用する必要があります。重要な構造/設計関連のコードまたは重要な操作は、クラスまたはインターフェイスから構成/継承されるオブジェクトに配置する必要があります。

別のオブジェクトが拡張オブジェクトを使用しようとすると、拡張機能が表示されず、再実装/再参照が必要になる場合があります。

従来の知恵では、拡張メソッドは次の場合にのみ使用する必要があります。

  • Vaibhavが述べたように、ユーティリティクラス
  • 封印されたサードパーティ API の拡張
于 2008-08-12T07:23:55.130 に答える
9

拡張メソッドの賢明な使用は、インターフェースを(抽象)基本クラスとより同等の位置に置くと思います。


バージョニング。基本クラスがインターフェースよりも優れている点の1つは、新しい仮想メンバーを新しいバージョンで簡単に追加できることです。一方、メンバーをインターフェースに追加すると、古いバージョンのライブラリに対して構築された実装者が壊れます。代わりに、新しいメンバーを含む新しいバージョンのインターフェイスを作成する必要があります。ライブラリは、元のインターフェイスのみを実装するレガシーオブジェクトへのアクセスを回避または制限する必要があります。

具体的な例として、ライブラリの最初のバージョンでは、次のようなインターフェイスを定義できます。

public interface INode {
  INode Root { get; }
  List<INode> GetChildren( );
}

ライブラリがリリースされると、現在のユーザーを壊さずにインターフェイスを変更することはできません。代わりに、次のリリースでは、機能を追加するための新しいインターフェイスを定義する必要があります。

public interface IChildNode : INode {
  INode Parent { get; }
}

ただし、新しいライブラリのユーザーのみが新しいインターフェイスを実装できます。レガシーコードを操作するには、拡張メソッドが適切に処理できる古い実装を適応させる必要があります。

public static class NodeExtensions {
  public INode GetParent( this INode node ) {
    // If the node implements the new interface, call it directly.
    var childNode = node as IChildNode;
    if( !object.ReferenceEquals( childNode, null ) )
      return childNode.Parent;

    // Otherwise, fall back on a default implementation.
    return FindParent( node, node.Root );
  }
}

これで、新しいライブラリのすべてのユーザーは、レガシー実装と最新の実装の両方を同じように扱うことができます。


過負荷。拡張メソッドが役立つもう1つの領域は、インターフェースメソッドにオーバーロードを提供することです。アクションを制御するためのいくつかのパラメーターを持つメソッドがある場合がありますが、90%の場合、最初の1つまたは2つだけが重要です。C#ではパラメーターのデフォルト値を設定できないため、ユーザーは毎回完全にパラメーター化されたメソッドを呼び出すか、すべての実装でコアメソッドの簡単なオーバーロードを実装する必要があります。

代わりに、拡張メソッドを使用して、簡単なオーバーロードの実装を提供できます。

public interface ILongMethod {
  public bool LongMethod( string s, double d, int i, object o, ... );
}

...
public static LongMethodExtensions {
  public bool LongMethod( this ILongMethod lm, string s, double d ) {
    lm.LongMethod( s, d, 0, null );
  }
  ...
}


これらのケースは両方とも、インターフェースによって提供される操作の観点から書かれており、些細な、またはよく知られているデフォルトの実装が含まれていることに注意してください。とは言うものの、クラスから継承できるのは1回だけであり、拡張メソッドを的を絞って使用することで、インターフェースに欠けている基本クラスによって提供されるいくつかの優れた点に対処するための貴重な方法を提供できます:)


編集: Joe Duffyによる関連記事:デフォルトのインターフェースメソッド実装としての拡張メソッド

于 2008-09-01T21:52:48.700 に答える
6

拡張メソッドが置き換える最良のものは、すべてのプロジェクトにあるすべてのユーティリティクラスだと思います。

少なくとも今のところ、Extensionメソッドを他に使用すると、職場で混乱が生じると思います。

私の2ビット。

于 2008-08-11T18:56:39.117 に答える
2

もうちょっと。

複数のインターフェイスに同じ拡張メソッドシグネチャがある場合は、呼び出し元を1つのインターフェイスタイプに明示的に変換してから、メソッドを呼び出す必要があります。例えば

((IFirst)this).AmbigousMethod()
于 2010-01-08T04:06:05.643 に答える
2

拡張メソッドを使用してドメイン/モデルと UI/ビューの機能を分離することは、特にそれらが別々の名前空間に存在できるため、良いことだと思います。

例えば:

namespace Model
{
    class Person
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
}

namespace View
{
    static class PersonExtensions
    {
        public static string FullName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName + " " + p.Surname;
        }

        public static string FormalName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName[0] + ". " + p.Surname;
        }
    }
}

このようにして、XAML データ テンプレートと同様に拡張メソッドを使用できます。クラスのプライベート/保護されたメンバーにアクセスすることはできませんが、アプリケーション全体でコードが過度に重複することなく、データの抽象化を維持できます。

于 2008-08-17T09:00:27.943 に答える
2

インターフェイスを拡張することに問題はありません。実際、LINQ が拡張メソッドをコレクション クラスに追加する方法です。

そうは言っても、実際には、そのインターフェースを実装するすべてのクラスで同じ機能を提供する必要があり、その機能が派生物の「公式」実装の一部ではない (おそらくそうすべきではない) 場合にのみ、これを行う必要があります。クラス。新しい機能を必要とする可能性のあるすべての派生型に対して拡張メソッドを記述することが現実的でない場合にも、インターフェイスを拡張することは有効です。

于 2008-08-17T04:40:58.843 に答える
1

共通の機能を共有するために基本クラスを使用することを提唱する多くの人々を目にします。これには注意してください。継承よりも合成を優先する必要があります。継承は、モデリングの観点から意味がある場合にのみ、ポリモーフィズムに使用する必要があります。これは、コードの再利用に適したツールではありません。

質問について:これを行うときは制限に注意してください-たとえば、示されているコードでは、拡張メソッドを使用してGetChildrenを効果的に実装し、この実装を効果的に「シール」し、必要に応じてIHaveChildrenimplが独自のものを提供できないようにします。これでよければ、拡張メソッドのアプローチはそれほど気にしません。それは石に固定されておらず、通常、後でより多くの柔軟性が必要になったときに簡単にリファクタリングできます。

柔軟性を高めるために、戦略パターンを使用することが望ましい場合があります。何かのようなもの:

public interface IHaveChildren 
{
    string ParentType { get; }
    int ParentId { get; }
}

public interface IChildIterator
{
    IEnumerable<IChild> GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

    public DefaultChildIterator(IHaveChildren parent)
    {
        _parent = parent; 
    }

    public IEnumerable<IChild> GetChildren() 
    { 
        // default child iterator impl
    }
}

public class Node : IHaveChildren, IChildIterator
{ 
    // *snip*

    public IEnumerable<IChild> GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}
于 2008-08-18T07:21:20.140 に答える
1

ああ。インターフェイスを拡張しないでください。インターフェイスは、クラスが実装する必要があるクリーンなコントラクトであり、これが正しく機能するためには、これらの
クラスの使用をコア インターフェイスにあるものに制限する必要があります。

そのため、実際のクラスではなく、常にインターフェイスを型として宣言します。

IInterface variable = new ImplementingClass();

右?

何らかの追加機能を備えたコントラクトが本当に必要な場合は、抽象クラスが役に立ちます。

于 2008-08-12T07:38:23.650 に答える
1

Rob Connery (Subsonic および MVC Storefront) は、彼の Storefront アプリケーションに IRepository のようなパターンを実装しました。上記のパターンとはまったく異なりますが、いくつかの類似点があります。

データ レイヤーは IQueryable を返します。これにより、消費レイヤーはその上にフィルター処理と並べ替え式を適用できます。おまけに、たとえば GetProducts メソッドを 1 つ指定してから、その並べ替え、フィルタリング、または特定の範囲の結果だけをどのように処理するかを消費層で適切に決定できます。

伝統的なアプローチではありませんが、非常にクールで、間違いなく DRY のケースです。

于 2008-10-15T20:32:03.143 に答える
0

私が見ることができる問題の1つは、大企業では、このパターンにより、誰もが理解して使用するのが(不可能ではないにしても)コードが難しくなる可能性があることです。複数の開発者が、それらのクラスとは別に、既存のクラスに独自のメソッドを絶えず追加している場合(そして、神は私たち全員をBCLクラスにさえ助けてくれます)、コードベースがかなり速く制御不能になるのを見ることができます。

私自身の仕事でも、これが起こっているのを見ることができました。私たちが取り組んでいるコードのすべてのビットをUIまたはデータアクセス層に追加したいというPMの願望により、彼が20または30のメソッドを追加することを主張しているのを完全に見ることができました。文字列処理に接線方向にのみ関連するSystem.String。

于 2008-08-11T18:21:37.283 に答える
0

同様のことを解決する必要がありました。List<IIDable> を拡張関数に渡す必要がありました。ここで、IIDable は長い getId() 関数を持つインターフェイスです。GetIds(this List<IIDable> bla) を使用しようとしましたが、コンパイラで許可されませんでした。代わりにテンプレートを使用し、関数内でインターフェイス型にキャストした型を使用しました。いくつかの linq to sql 生成クラスにこの関数が必要でした。

これが役立つことを願っています:)

    public static List<long> GetIds<T>(this List<T> original){
        List<long> ret = new List<long>();
        if (original == null)
            return ret;

        try
        {
            foreach (T t in original)
            {
                IIDable idable = (IIDable)t;
                ret.Add(idable.getId());
            }
            return ret;
        }
        catch (Exception)
        {
            throw new Exception("Class calling this extension must implement IIDable interface");
        }
于 2009-12-07T10:26:19.300 に答える