web-dev-qa-db-ja.com

IList <T>を返すのは、T []またはList <T>を返すよりも悪いですか?

このような質問への回答: List <T>またはIList <T> は、コレクションの具体的な実装を返すよりもインターフェースを返す方が良いことに常に同意しているようです。しかし、私はこれに苦労しています。インターフェイスをインスタンス化することは不可能であるため、メソッドがインターフェイスを返す場合、実際にはまだ特定の実装を返しています。私は2つの小さなメソッドを書くことでこれを少し試していました:

_public static IList<int> ExposeArrayIList()
{
    return new[] { 1, 2, 3 };
}

public static IList<int> ExposeListIList()
{
    return new List<int> { 1, 2, 3 };
}
_

そして、私のテストプログラムでそれらを使用します:

_static void Main(string[] args)
{
    IList<int> arrayIList = ExposeArrayIList();
    IList<int> listIList = ExposeListIList();

    //Will give a runtime error
    arrayIList.Add(10);
    //Runs perfectly
    listIList.Add(10);
}
_

どちらの場合も新しい値を追加しようとすると、コンパイラーはエラーを返しませんが、明らかに、配列を_IList<T>_として公開するメソッドは、何かを追加しようとするとランタイムエラーを返します。したがって、私のメソッドで何が起こっているのか分からず、それに値を追加する必要がある場合、エラーを発生させることなく値を追加できるように、最初にIListListにコピーする必要があります。もちろん、彼らはListまたはArrayを処理しているかどうかを確認するために型チェックを行うことができますが、そうしない場合はコレクションにアイテムを追加します既にIListであっても、ListListにコピーする他の選択肢はありません。配列がIListとして公開されることはありませんか?

私のもう一つの懸念は、 リンクされた質問 (強調鉱山)の受け入れられた答えに基づいています:

他の人が使用するライブラリを介してクラスを公開している場合、通常、具体的な実装ではなくインターフェイスを介して公開したいこれは、後で使用するためにクラスの実装を変更する場合に役立ちます別の具体的なクラス。その場合、インターフェイスが変更されないため、ライブラリのユーザーがコードを更新する必要はありません。

内部で使用しているだけの場合は、それほど気にする必要はなく、Listを使用しても大丈夫です。

誰かが実際に値を追加/削除するために私のExposeListIlist()メソッドから得た_IList<T>_を実際に使用したと想像してください。すべて正常に動作します。しかし、今の答えが示唆するように、インターフェイスを返すことはより柔軟なので、リストの代わりに配列を返します(私の側には問題ありません!)、そして彼らは御treat走のためにいます...

TLDR:

1)インターフェイスを公開すると、不要なキャストが発生しますか?それは問題ではありませんか?

2)場合によっては、ライブラリのユーザーがキャストを使用しないと、メソッドが完全に正常であっても、メソッドを変更するとコードが破損することがあります。

私はおそらくこれを考えすぎていますが、インターフェイスを返すことは実装を返すことよりも優先されるという一般的なコンセンサスは得られません。

80
Alexander Derck

たぶん、これはあなたの質問に直接答えているわけではありませんが、.NET 4.5+では、パブリックAPIまたは保護されたAPIを設計するときにこれらのルールに従うことを好みます。

  • 列挙のみが使用可能な場合、IEnumerable<T>を返します。
  • 列挙とアイテム数の両方が利用可能な場合は、IReadOnlyCollection<T>を返します。
  • 列挙、項目カウント、インデックス付きアクセスが利用可能な場合、IReadOnlyList<T>を返します。
  • 列挙、項目カウント、および変更が利用可能な場合は、ICollection<T>を返します。
  • 列挙、項目数、インデックス付きアクセスおよび変更が利用可能な場合、IList<T>を返します。

最後の2つのオプションは、そのメソッドがIList<T>実装として配列を返してはならないことを前提としています。

107
Dennis

いいえ、消費者は正確に何を知っている必要があるため IList ::

IListはICollectionインターフェイスの子孫であり、すべての非ジェネリックリストの基本インターフェイスです。 IListの実装は、読み取り専用、固定サイズ、可変サイズの3つのカテゴリに分類されます。読み取り専用のIListは変更できません。固定サイズのIListでは、要素の追加または削除はできませんが、既存の要素の変更はできます。可変サイズのIListでは、要素の追加、削除、変更が可能です。

IList.IsFixedSizeおよびIList.IsReadOnlyを確認して、必要な処理を実行できます。

IListはファットインターフェイスの例であり、複数の小さなインターフェイスに分割する必要があり、配列をIListとして返すときに Liskov置換の原則 にも違反していると思います。

続きを読む インターフェイスを返すことについて決定したい場合


[〜#〜] update [〜#〜]

さらに掘り下げてみると、IList<T>IListを実装しておらず、IsReadOnlyがベースインターフェイスからアクセスできます ICollection<T> ですが、IList<T>にはIsFixedSizeがありません。 なぜ汎用IList <>が非汎用IListを継承しないのか? の詳細を読む

31
Hamid Pourjam

すべての「インターフェース対実装」の質問と同様に、パブリックメンバーを公開することの意味を理解する必要があります。このクラスのパブリックAPIを定義します。

_List<T>_をメンバー(フィールド、プロパティ、メソッドなど)として公開する場合、そのメンバーのコンシューマーに次のように伝えます:このメソッドにアクセスして取得した型is_List<T>_、またはその派生物。

インターフェイスを公開する場合、具体的な型を使用してクラスの「実装の詳細」を非表示にします。もちろん_IList<T>_をインスタンス化することはできませんが、_Collection<T>_、_List<T>_、それらの派生、または_IList<T>_を実装する独自の型を使用できます。

actual質問は "なぜArrayは_IList<T>_"を実装するのか?、または "なぜ_IList<T>_インターフェイスが非常に多くのメンバーを持っている"

また、そのメンバーの消費者に何をしてほしいかにも依存します。 _Expose..._メンバーを通じて実際に内部メンバーを返す場合は、とにかくnew List<T>(internalMember)を返します。そうしないと、コンシューマーはそれらを_IList<T>_にキャストして、それを通じて内部メンバー。

消費者が結果を繰り返すことを期待している場合は、代わりに_IEnumerable<T>_または_IReadOnlyCollection<T>_を公開します。

12
CodeCaster

文脈から外れた包括的な引用符には注意してください。

インターフェースを返すことは、具体的な実装を返すことよりも優れています

この引用は、 SOLID原則 のコンテキストで使用される場合にのみ意味があります。 5つの原則がありますが、この議論の目的のために、最後の3つについてお話しします。

依存関係の反転の原理

「抽象化に依存する。コンクリートに依存しないでください。」

私の意見では、この原則は最も理解しにくいものです。しかし、引用を注意深く見ると、元の引用によく似ています。

インターフェイス(抽象化)に依存します。具体的な実装(コンクリート)に依存しないでください。

これはまだ少し混乱しますが、他の原則を一緒に適用し始めると、もっと意味があり始めます。

リスコフ置換原理

「プログラム内のオブジェクトは、そのプログラムの正確性を変更することなく、サブタイプのインスタンスで置き換え可能でなければなりません。」

指摘したように、Arrayを返すことは、List<T>を実装しているにもかかわらず、IList<T>を返すこととは明らかに異なる動作です。これは間違いなくLSPの違反です。

実現する重要なことは、インターフェースはconsumerについてであるということです。インターフェイスを返す場合、そのインターフェイスのメソッドまたはプロパティはプログラムの動作を変更せずに使用できるというコントラクトを作成しました。

インターフェイス分離の原則

「多くのクライアント固有のインターフェイスは、1つの汎用インターフェイスよりも優れています。」

インターフェイスを返す場合は、実装がサポートする最もクライアント固有のインターフェイスを返す必要があります。つまり、クライアントがAddメソッドを呼び出すことを期待していない場合は、Addメソッドを含むインターフェイスを返さないでください。

残念ながら、.NETフレームワークのインターフェイス(特に初期のバージョン)は、必ずしも理想的なクライアント固有のインターフェイスではありません。 @Dennisが答えで指摘したように、.NET 4.5+にはさらに多くの選択肢があります。

8
craftworkgames

インターフェイスを返すことは、コレクションの具体的な実装を返すことよりも必ずしも良いというわけではありません。具体的な型の代わりにインターフェイスを使用するのには、常に十分な理由が必要です。あなたの例では、そうするのは無意味なようです。

インターフェイスを使用する正当な理由は次のとおりです。

  1. インターフェースを返すメソッドの実装がどのようになるかはわかりませんが、時間の経過とともに開発されるものが多数ある可能性があります。他の会社から、それらを書いている他の人かもしれません。したがって、必要なものだけに同意し、機能の実装方法はユーザーに任せます。

  2. タイプセーフな方法で、クラス階層から独立したいくつかの共通機能を公開する必要があります。同じメソッドを提供する異なるベースタイプのオブジェクトは、インターフェイスを実装します。

1と2は基本的に同じ理由であると主張することができます。これらは、最終的に同じニーズにつながる2つの異なるシナリオです。

「それは契約です」。契約があなた自身のものであり、アプリケーションが機能と時間の両方で閉じられている場合、多くの場合、インターフェースを使用しても意味がありません。

4
Martin Maat