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


80

次のような質問への回答: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>何かを追加しようとするとランタイムエラーが発生します。したがって、私のメソッドで何が起こっているのかわからず、それに値を追加する必要がある人は、エラーのリスクを冒さずに値を追加できるようにIList、最初に私のをにコピーListする必要があります。もちろん、タイプチェックを実行して、Listまたはを処理しているかどうかを確認できますArrayが、それを実行せず、コレクションIListListListアイテムを追加したい場合をコピーする他の選択肢はありません。すでにです。アレイは決して公開されるべきではありませんIListか?

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

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

内部で使用しているだけの場合は、それほど気にしないかもしれませんし、リストを使用しても問題ないかもしれません。

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

TLDR:

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

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

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


9
その後、戻っIEnumerable<T>てコンパイル時の安全性を確保します。通常は特定の型にキャストすることでパフォーマンスを最適化しようとするすべてのLINQ拡張メソッドを引き続き使用できます(プロパティを列挙する代わりICollection<T>に使用するようにCount)。
ティムシュメルター2015

4
配列IList<T>は「ハック」によって実装されます。それを返すメソッドを公開したい場合は、実際にそれを実装している型を返します。
codeCaster 2015

3
守れない約束をしないでください。クライアントプログラマーは、あなたの「リストを返します」という契約書を読んだときにがっかりする可能性がかなりあります。そうではありません。そうしないでください。
ハンスパッサント2015

7
MSDNから:IListはICollectionインターフェイスの子孫であり、すべての非ジェネリックリストの基本インターフェイスです。IListの実装は、読み取り専用、固定サイズ、可変サイズの3つのカテゴリに分類されます。読み取り専用のIListは変更できません。固定サイズのIListでは要素の追加や削除はできませんが、既存の要素の変更はできます。可変サイズのIListを使用すると、要素を追加、削除、および変更できます。
Hamid Pourjam 2015

3
@AlexanderDerck:消費者がリストにアイテムを追加できるようにしたい場合IEnumerable<T>は、そもそもリストに追加しないでください。その後、IList<T>またはに戻っても問題ありませんList<T>
ティムシュメルター2015

回答:


108

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

  • IEnumerable<T>列挙のみが利用可能な場合は、戻ります。
  • IReadOnlyCollection<T>列挙型とアイテム数の両方が利用可能な場合は戻ります。
  • IReadOnlyList<T>列挙、アイテム数、およびインデックス付きアクセスが利用可能な場合は、戻ります。
  • ICollection<T>列挙、アイテム数、および変更が利用可能な場合は戻ります。
  • IList<T>列挙、アイテム数、インデックス付きアクセス、および変更が利用可能な場合は、戻ります。

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


2
確かに答えではありませんが、経験則として非常に役立ちます:)最近、何を返すかを選択するのに苦労しています。乾杯!
Alexander Derck 2015

1
これは確かに正しい原則+1です。ただ、完全を期すために、私は追加するIReadOnlyCollection<T>ICollection<T>非インデックス可能なコンテナのためのそれらの使用法を持っている(例えば、後にEFのエンティティのサブコレクションのナビゲーションプロパティのデファクトスタンダードAがある)
イワンStoev

@IvanStoev回答を追加して編集していただけますか?最終的にどこでどのインターフェースを使用するかのリストを入手できればいいのですが
Alexander Derck 2015

3
とC ++と同じ欠陥があることに注意する必要がIReadOnlyCollection<T>ありIReadOnlyList<T>ますconst。コレクションには不変であるか、コレクションへのアクセス方法のみがコレクションの変更を禁止している可能性があります。そして、あなたはお互いを区別することはできません。
2015

1
@Panzercrisis:もちろん、2番目のオプションを選択します- IEnumerable<T>。API開発者は、許可されているユースケースについて完全な知識を持っています。メソッドの結果を列挙することだけを目的としている場合は、を公開する理由はありませんIList<T>。結果の処理が少ないほど、メソッドの実装はより柔軟になります。たとえば、公開IEnumerable<T>すると実装をyield returnsに変更できますがIList<T>、これはできません。
デニス

31

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

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

あなたはそれをチェックしてIList.IsFixedSize、あなたが望むことをすることができIList.IsReadOnlyます。

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

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


更新

より多くの掘削と私はそれが見つかりませIList<T>実装していないIListIsReadOnlyベースのインタフェースを介してアクセス可能であるICollection<T>が、何があるIsFixedSizeためIList<T>ジェネリックIList <>が非ジェネリックIListを継承しない理由の詳細をお読みください


8
これらのプロパティは優れていますが、それでも私の意見ではインターフェイスの目的に反しています。私にとって、インターフェースは契約であり、チェックなどに依存するべきではありません
Alexander Derck 2015

1
(非固定サイズ/非読み取り専用)List<T>セマンティクスを持つ追加のインターフェイスを提供していたとしたら、開発者にとっては本当に親切だったでしょうが、これが含まれなかった理由についてはおそらく確かな理由があります。しかし、はい、さまざまな機能をカバーするいくつかのインターフェースについてのあなたの考えは、はるかに健全な選択だと思います。
支出者2015

1
IList<T>ファットなインターフェースですが、配列リストから取得できるものの中で最も多いものです。したがって、複数のインターフェイスに分割された場合、使用するすべてのメソッド(IList.Countまたはインデクサーなど)がサポートされている場合でも、リストと配列で一部のメソッドを使用できず、そのうちの1つでしか使用できない可能性があります。だから私の意見では、それは良い決断でした。それが発生した場合NotSupportedException1を使用することを望んでいる非常に少数の例ではAdd、アレイ上に、うまく、それが契約です。
ティムシュメルター2015

1
@dotctorなんと偶然なのか、昨日リスコフの置換原則について読んでいたので、実際に考えさせられました:)答えてくれてありがとう
Alexander Derck 2015

6
デニスのルールと組み合わせたこの答えは、このジレンマに対処する方法の理解を確かに向上させることができます。言うまでもなく、リスコフの置換原則は.NET Frameworkのあちこちで違反されており、特に心配する必要はありません。
fabjan 2015

13

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

あなたが公開した場合List<T>メンバー(フィールド、プロパティ、メソッド、...)として、あなたはそのメンバーの消費者に伝える:このメソッドにアクセスすることによって得られるタイプであるList<T>ことの派生、または何か。

ここで、インターフェースを公開する場合、具象型を使用してクラスの「実装の詳細」を非表示にします。もちろん、あなたがインスタンス化することはできませんIList<T>が、あなたは使用することができCollection<T>List<T>その派生したり、独自のタイプの実装をIList<T>

実際の質問は、「なぜArray実装IList<T>、または「なぜ持っているIList<T>多くのメンバーそうインターフェースを」

また、そのメンバーのコンシューマーに何をしてほしいかによっても異なります。実際にメンバーを介して内部メンバーを返す場合はExpose...new List<T>(internalMember)とにかく戻りたいと思うでしょう。そうしないと、コンシューマーがそれらをキャストしIList<T>て内部メンバーを変更する可能性があります。

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


3
「実際の質問は、「なぜArrayがIList <T>を実装するのか」、または「なぜIList <T>インターフェイスに非常に多くのメンバーがあるのか​​」です。
Alexander Derck 2015

2
@ Fabjan-IList<T>アイテムを追加および削除するメソッドがあります。そのため、配列を実装するのは良くありません。このIsReadOnlyプロパティは、(少なくとも)2つのインターフェイスに分割する必要がある場合に、違いを理解するためのハックです。
リー

@Leeおっと、あなたは正しいです、私の不正確なコメントを削除します
Fabjan 2015

1
私が以前に求めてきました、それの何か@AlexanderDerck stackoverflow.com/q/5968708/706456
oleksii

@oleksii確かに似ていますが、重複とは言いません
Alexander Derck 2015

8

文脈から外れた一重引用符には注意してください。

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

この引用は、SOLIDの原則の文脈で使用される場合にのみ意味があります。5つの原則がありますが、この説明では、最後の3つについてのみ説明します。

依存性逆転の原則

「抽象化に依存する必要があります。結石に頼らないでください。」

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

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

これはまだ少し混乱しますが、他の原則を一緒に適用し始めると、はるかに理にかなっています。

リスコフの置換原則

「プログラム内のオブジェクトは、そのプログラムの正確さを変更することなく、サブタイプのインスタンスで置き換えることができる必要があります。」

ご指摘のとおり、両方がを実装している場合でも、を返すことArrayは、を返すこととは明らかに異なる動作です。これは間違いなくLSPの違反です。List<T>IList<T>

認識しておくべき重要なことは、インターフェースは消費者に関するものであるということです。インターフェイスを返す場合は、プログラムの動作を変更せずに、そのインターフェイスの任意のメソッドまたはプロパティを使用できるというコントラクトを作成しました。

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

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

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

残念ながら、.NET Framework(特に初期バージョン)のインターフェイスは、必ずしも理想的なクライアント固有のインターフェイスであるとは限りません。@Dennisが彼の回答で指摘したように、.NET4.5以降にはさらに多くの選択肢があります。


リスコフの原則を読んだことが、そもそも私を混乱させたのですIList<T>
Alexander Derck 2015

特定のケースでは、LSPに違反するのはアレイです。IListを返す場合は、この違反があるため、配列を返さないでください。発信者とは、アイテムを追加および削除できるリストを取得するという契約をすでに結んでいます。
craftworkgames

5

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

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

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

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

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

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


1
私は本当に同意しません。インターフェースを返すことが可能であれば、必要なのはそれだけなので、インターフェースを返します。メソッドがインターフェースを期待している場合、そのインターフェースのみを実装するクラスだけでなく、インターフェースと他のものの負荷を実装するクラスも渡すことができます。インターフェイスを使用すると、既存のものを書き直すことなくアプリケーションを拡張するのが簡単になります
Alexander Derck 2015

2
「他の誰かがこのクラス(ライブラリ)を使用した場合はどうなるか」という考え方で、すべてのソフトウェア(特にクラスとクラスライブラリ)を開発することは本当に啓発的です。および「返したい具象型に機能を追加したい場合はどうすればよいですか?」。BCLタイプは封印できるため、拡張することはできません。次に、APIがインターフェースではなく、その正確な型を公開することを約束しているため、何か他のものを返したい場合は行き詰まります。ほとんどの場合、インターフェイスを返す方が、具体的な実装を返すよりも優れています。
codeCaster 2015

1
私は(本当に)同意します。3番目の理由を追加する必要がありました。「呼び出し元へのメッセージを明確にするために、返されるオブジェクトの機能を本質に限定する」。コレクションのアセンブルに使用される本格的なオブジェクトではなく、可能な限りIReadOnlyXxxインターフェイスを返します。
マーティンマート2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.