List <T>またはIList <T> [終了]


495

C#でリストよりもIListを使用する理由を誰かに説明してもらえますか?

関連質問:なぜ公開するのが悪いと考えられているのですList<T>


1
「実装ではなくインターフェイスへのコード」をインターネットで検索すると、一日中読むことができます。いくつかのh0ly戦争を見つけることができます。
granadaCoder

回答:


446

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

内部で使用しているだけであれば、それほど気にする必要はなく、使用してもかまいませんList<T>


19
(微妙な)違いを理解していませんが、私に指摘できる例はありますか?
StingyJack 2008

134
最初にList <T>を使用していて、IList <T>を実装する特殊なCaseInsensitiveList <T>を使用するように変更したいとします。具象型を使用する場合、すべての呼び出し元を更新する必要があります。IList <T>として公開されている場合、呼び出し元を変更する必要はありません。
tvanfosson 2008

46
ああ。OK。わかった。契約の最小公分母を選ぶ。ありがとう!
StingyJack 2008

16
この原則は、オブジェクト指向プログラミングの3つの柱の1つであるカプセル化の例です。アイデアは、実装の詳細をユーザーから隠し、代わりに安定したインターフェースを提供することです。これは、将来変更される可能性のある詳細への依存を減らすためです。
ジェイソン

14
T []:IList <T>にも注意してください。パフォーマンス上の理由から、プロシージャからList <T>を返すことから常にT []を返すこと、およびプロシージャがIList <T>を返すように宣言されている場合は常に入れ替えることができます。 (またはおそらくICollection <T>、またはIEnumerable <T>)他に何も変更する必要はありません。
yfeldblum 2008

322

それほど一般的ではない答えは、プログラマーがソフトウェアを世界中で再利用するふりをするようなものであり、プロジェクトの大部分は実際には少数の人々によって維持され、インターフェイスに関連した適切なサウンドバイトがどれほどであるかはわかりません。あなた自身。

建築宇宙飛行士。.NETフレームワークに既にあるものに何かを追加する独自のIListを作成する可能性は非常に低いため、理論的には「ベストプラクティス」のために用意されたゼリーです。

ソフトウェア宇宙飛行士

インタビューでどちらを使用するかを尋ねられているのは明らかですが、IListと言って微笑みます。どちらも、自分がとても賢いことを喜んでいます。または、一般向けAPIのIListの場合。うまくいけば、あなたは私のポイントを得ます。


65
私はあなたの皮肉な答えに同意しなければなりません。私は、オーバーアーキテクチャが本当の問題であるという感情に完全に同意しません。しかし、インターフェイスが本当に素晴らしいコレクションの場合は特にそう思います。を返す関数があるとしますIEnumerable<string>。関数の内部ではList<string>、内部バッキングストアにを使用してコレクションを生成できますが、呼び出し元にそのコンテンツを列挙するだけで、追加や削除はさせないようにします。インターフェイスをパラメータとして受け入れると、「文字列のコレクションが必要ですが、変更しないでください」という同様のメッセージが送信されます。
joshperry 2010

38
ソフトウェア開発はすべて、意図を翻訳することです。インターフェースが大きすぎて壮大なアーキテクチャを構築するためだけに役立ち、小さな店には場所がないと思うなら、インタビューであなたの向かいに座っている人が私ではないことを願っています。
joshperry 2010

48
誰かがこのアレックを言わなければならなかった...結果としてあらゆる種類の学術的パターンが憎悪メールを追いかけているとしても。小さなアプリにインターフェースが読み込まれていて「定義を見つける」をクリックしたときにそれを嫌う私たち全員の+1は、問題の原因以外の場所に連れて行ってくれます。重宝すると思います。
ガット、

6
可能な場合は、この回答に対して1000ポイントを差し上げます。:D
Samuel

8
私はパターン追跡のより広い結果について話していました。一般的なインターフェースのインターフェースは良いですが、パターンを追跡するための追加のレイヤーが悪いです。
ガット2013

186

インターフェイスは約束(または契約)です。

それは常に約束のとおりです- 小さいほど良いです。


56
常に優れているとは限らず、保管しやすいだけです。:)
Kirk Broadhurst

5
これはわかりません。IList約束もそうです。ここでどちらが小さく、より良い約束ですか?これは論理的ではありません。
nawfal 14年

3
他のクラスについては、私は同意します。しかし、ではないためList<T>ので、AddRangeインターフェイスで定義されていません。
Jesse de Wit

3
これは、この特定のケースでは本当に役に立ちません。最善の約束は守られるものです。IList<T>守れない約束をする。たとえば、たまたま読み取り専用のAdd場合にスローできるメソッドがありますIList<T>List<T>一方、常に書き込み可能であるため、常に約束が守られます。また、.NET 4.6以降ではIReadOnlyCollection<T>、がIReadOnlyList<T>あり、ほとんどの場合がよりも適していますIList<T>。そして、それらは共変です。避けてくださいIList<T>
ZunTzu 2017年

@ZunTzu不変のコレクションを変更可能なコレクションとして渡している誰かが提示された契約を故意に破ったと主張します-それは契約自体の問題ではありませんIList<T>。誰かが実装IReadOnlyList<T>してすべてのメソッドに例外をスローさせることもできます。これはアヒルのタイピングとは正反対のことです。アヒルと呼んでいますが、アヒルのようにはいられません。コンパイラがそれを強制できない場合でも、それは契約の一部です。私は100%ということに同意しますIReadOnlyList<T>IReadOnlyCollection<T>かかわらず、読み取り専用アクセスのために使用すべきです。
シェルビーオールドフィールド

93

一部の人々は「IList<T>代わりに常に使用する」と言いList<T>ます。
彼らはあなたにあなたのメソッドシグネチャをからvoid Foo(List<T> input)に変えて欲しいと思っていますvoid Foo(IList<T> input)

これらの人々は間違っています。

それよりも微妙です。をIList<T>パブリックインターフェイスの一部としてライブラリに返す場合は、将来的にカスタムリストを作成するための興味深いオプションを残します。あなたはそのオプションを必要としないかもしれませんが、それは議論です。具象型の代わりにインターフェースを返すためのすべての議論だと思います。言及する価値はありますが、この場合は重大な欠陥があります。

マイナーな反論として、すべての呼び出し元がList<T>とにかく必要であり、呼び出しコードが散らかっていることに気付くかもしれません.ToList()

しかし、はるかに重要なのは、IListをパラメーターとして受け入れる場合は、同じように動作しないためIList<T>、注意List<T>することです。名前が似ているにもかかわらず、インターフェースを共有しているにもかかわらず、同じコントラクトを公開していません

このメソッドがあるとします:

public Foo(List<int> a)
{
    a.Add(someNumber);
}

参考になる同僚が、受け入れる方法を「リファクタリング」しますIList<int>

はをint[]実装しているため、コードは壊れてIList<int>いますが、サイズは固定されています。ICollection<T>(のベースIList<T>)のコントラクトにIsReadOnlyは、コレクションにアイテムを追加または削除する前に、それを使用してフラグをチェックするコードが必要です。の契約でList<T>はありません。

Liskov置換原則(簡略化)は、派生型を基本型の代わりに使用でき、追加の前提条件や事後条件がなくてもよいと述べています。

これは、リスコフの代替原理に違反しているように感じます。

 int[] array = new[] {1, 2, 3};
 IList<int> ilist = array;

 ilist.Add(4); // throws System.NotSupportedException
 ilist.Insert(0, 0); // throws System.NotSupportedException
 ilist.Remove(3); // throws System.NotSupportedException
 ilist.RemoveAt(0); // throws System.NotSupportedException

しかし、そうではありません。これに対する答えは、例が誤ってIList <T> / ICollection <T>を使用したことです。ICollection <T>を使用する場合は、IsReadOnlyフラグを確認する必要があります。

if (!ilist.IsReadOnly)
{
   ilist.Add(4);
   ilist.Insert(0, 0); 
   ilist.Remove(3);
   ilist.RemoveAt(0);
}
else
{
   // what were you planning to do if you were given a read only list anyway?
}

誰かが配列またはリストを渡した場合、フラグを毎回チェックしてフォールバックがあれば、コードは正常に動作します...しかし、本当に。誰がやるの?メソッドが追加のメンバーを取得できるリストを必要とするかどうかは、事前にわかりません。メソッドシグネチャでそれを指定しませんか?のような読み取り専用リストが渡された場合、正確にはどうするのint[]でしょうか。

/を正しくList<T>使用するコードにを代入できます。あなたはできませんあなたが代用できることを保証/ コードに使用していること。IList<T>ICollection<T> IList<T>ICollection<T>List<T>

可能な限り狭いインターフェースに依存する、具体的な型の代わりに抽象化を使用するという多くの議論において、単一責任原則/インターフェース分離原則に魅力があります。ほとんどの場合、をList<T>使用していて、代わりにより狭いインターフェースを使用できると考えている場合-なぜIEnumerable<T>ですか?これは、アイテムを追加する必要がない場合に適しています。コレクションに追加する必要がある場合は、具象型を使用してくださいList<T>

私にとってIList<T>(そしてICollection<T>)は.NETフレームワークの最悪の部分です。 IsReadOnly最小の驚きの原則に違反しています。のようなArray、項目の追加、挿入、または削除を決して許可しないクラスは、Add、Insert、およびRemoveメソッドを備えたインターフェースを実装してはなりません。(/software/306105/implementing-an-interface-when-you-dont-need-one-of-the-propertiesも参照してください

あるIList<T>組織に適しては?同僚から、メソッドシグネチャをのIList<T>代わりに使用するように変更するように依頼された場合はList<T>、要素をに追加する方法を同僚に尋ねますIList<T>。彼らが知らない場合IsReadOnly(そしてほとんどの人が知らない場合)は使用しないでくださいIList<T>。ずっと。


IsReadOnlyフラグはICollection <T>から取得され、アイテムをコレクションに追加またはコレクションから削除できるかどうかを示します。しかし、本当に混乱させるために、それはそれらが置き換えられるかどうかを示しません。これは、配列の場合(IsReadOnlys == trueを返す)は可能です。

IsReadOnlyの詳細については、ICollection <T> .IsReadOnlyのmsdn定義を参照してください。


5
すばらしい答えです。私はしても、それを追加したいIsReadOnlyですtrueのためにIList、あなたはまだ、現在のメンバーを変更することができます!おそらく、そのプロパティに名前を付ける必要がありIsFixedSizeますか?
xofz 2016年

(これはをArrayとして渡すことでテストされたIListため、現在のメンバーを変更できます)
xofz

1
@SamPearson .NET 1のIListにIsFixedSizeプロパティがありました。IsReadOnlyで統合された.NET 2.0の汎用IList <T>には含まれておらず、配列に関する驚くべき動作を引き起こしていました。微妙な違いに関する詳細な脳融点のブログはここにあります:enterprisecraftsmanship.com/2014/11/22/...
完璧主義

7
正解です。また、.NET 4.6以降IReadOnlyCollection<T>、がIReadOnlyList<T>あり、ほとんどの場合IList<T>、の危険なレイジーセマンティクスはありませんが、ほとんどの場合より適していますIEnumerable<T>。そして、それらは共変です。避けてくださいIList<T>
ZunTzu 2017年

37

List<T>はの特定の実装でありIList<T>T[]整数インデックスを使用して線形配列と同じ方法でアドレス指定できるコンテナです。あなたが指定した場合IList<T>、メソッドの引数の型として、あなただけがコンテナの特定の機能を必要とすることを指定します。

たとえば、インターフェース仕様は、特定のデータ構造の使用を強制しません。の実装はList<T>、線形配列として要素にアクセス、削除、および追加する場合と同じパフォーマンスになります。ただし、リンクリストに裏打ちされた実装を想像できます。この場合、末尾に要素を追加する方がコストは低くなりますが(一定時間)、ランダムアクセスの方がはるかに高価です。(.NET LinkedList<T>実装していませIList<T>。)

この例は、引数リストでインターフェースではなく実装を指定する必要がある場合があることも示しています。この例では、特定のアクセスパフォーマンス特性が必要な場合はいつでも。これは通常、コンテナの特定の実装で保証されています(List<T>ドキュメント:「IList<T>必要に応じてサイズが動的に増加する配列を使用して汎用インターフェースを実装します。」)。

さらに、必要な最小限の機能を公開することを検討してください。例えば。リストの内容を変更する必要がない場合は、拡張されるの使用を検討する必要がIEnumerable<T>ありIList<T>ます。


IListの代わりにIEnumerableを使用することに関する最後のステートメントについて。-これは常に推奨、単なるラッパーのIListオブジェクトを作成しますので、PERFの影響を持つことになります例えばWPFを取ることではありませんmsdn.microsoft.com/en-us/library/bb613546.aspx
ハーデス

1
すばらしい答えです。パフォーマンスの保証は、インターフェイスでは実現できないものです。一部のコードでは、これは非常に重要になる可能性があり、具象クラスを使用することで、意図、特定のクラスの必要性が伝達されます。一方、インターフェースは「このメソッドのセットを呼び出すだけでよいので、他の契約は意味しません」と述べています。
joshperry 2010

1
@joshperryインターフェースのパフォーマンスが気になる場合、そもそも間違ったプラットフォームにいることになります。インポ、これはマイクロ最適化です。
nawfal 14年

@nawfalインターフェイスのパフォーマンスの賛否両論についてコメントしていませんでした。具象クラスを引数として公開することを選択すると、パフォーマンスの意図を伝えることができるという事実に同意してコメントしました。撮るLinkedList<T>撮る対List<T>IList<T>すべて呼び出されるコードで必要とされる性能保証の何かを伝えます。
joshperry 14年

28

質問を少し変えて、具体的な実装よりもインターフェースを使用する理由を正当化するのではなく、インターフェースではなく具象実装を使用する理由を正当化しようとします。正当化できない場合は、インターフェースを使用してください。


1
それについて非常に興味深い考え方。そして、プログラミングするときに考える良い方法-ありがとう。
ピーナッツ

よく言った!インターフェースの方がより柔軟なアプローチであるため、具体的な実装によって何が購入されるのかを正当化する必要があります。
Cory House、

9
素晴らしい考え方。それでも私は答えることができます:私の理由はAddRange()と呼ばれます
PPC

4
私の理由はAdd()と呼ばれます。時々、あなたは確かにあなたが追加の要素を取ることできると知っているリストが欲しいですか?私の答えを見てください。
完璧主義者

19

IList <T>はインターフェイスであるため、別のクラスを継承してもIList <T>を実装できますが、List <T>を継承するとできません。

たとえば、クラスAがあり、クラスBがそれを継承している場合、List <T>は使用できません。

class A : B, IList<T> { ... }

15

TDDとOOPの原則は、一般に、実装ではなくインターフェースへのプログラミングです。

この特定のケースでは、基本的には言語の構成要素について話しているのであり、カスタムの構成要素については関係ありませんが、たとえば、Listが必要なものをサポートしていないことがわかりました。アプリの残りの部分でIListを使用していた場合は、Listを独自のカスタムクラスで拡張し、リファクタリングを行わずにそれを渡すことができます。

これを行うためのコストは最小限です。後で頭痛の種を保存しないのはなぜですか?それがインターフェースの原則です。


3
ExtendedList <T>がList <T>を継承する場合でも、List <T>コントラクトに適合させることができます。この引数は、sratchからIList <T>の独自の実装を作成する場合にのみ機能します(または少なくともList <T>を継承しない場合)
PPC

14
public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

この場合、IList <Bar>インターフェイスを実装する任意のクラスを渡すことができます。代わりにList <Bar>を使用した場合は、List <Bar>インスタンスのみを渡すことができます。

IList <Bar>ウェイは、List <Bar>ウェイよりも疎結合です。


13

これらのリストとIListの質問(または回答)のいずれも署名の違いについて言及していないことに驚いています。(これが私がSOでこの質問を検索した理由です!)

したがって、少なくとも.NET 4.5(2015年頃)以降、IListにないListに含まれるメソッドは次のとおりです

  • AddRange
  • AsReadOnly
  • BinarySearch
  • 容量
  • ConvertAll
  • 存在する
  • 探す
  • FindAll
  • FindIndex
  • FindLast
  • FindLastIndex
  • ForEach
  • GetRange
  • InsertRange
  • LastIndexOf
  • すべて削除する
  • RemoveRange
  • 逆行する
  • ソート
  • ToArray
  • TrimExcess
  • TrueForAll

1
完全に真実ではありません。Reverseそして、ToArrayのIEnumerable <T>インターフェイスの拡張方法のIList <T>導出するからです。
Tarec、2015年

これらはでのみ意味があり、Listの他の実装では意味がないためですIList
HankCa、2018

12

実装でインターフェースを使用する最も重要なケースは、APIのパラメーターです。APIがListパラメータを受け取る場合、それを使用する人は誰でもListを使用する必要があります。パラメータタイプがIListの場合、呼び出し元ははるかに自由になり、聞いたことのないクラスを使用できます。これは、コードの記述時に存在していなかった可能性もあります。


7

何.NET 5.0置き換える場合System.Collections.Generic.List<T>System.Collection.Generics.LinearList<T>。.NETは常に名前を所有しますList<T>が、それIList<T>が契約であることを保証します。したがって、私たち(少なくとも私)は、誰かの名前(この場合は.NETですが)を使用して後で問題が発生することは想定されていません。

を使用する場合IList<T>、呼び出し元は常に動作することが保証されており、実装者は基礎となるコレクションを任意の代替の具体的な実装に自由に変更できます。IList


7

すべての概念は、具体的な実装よりもインターフェースを使用する理由に関する上記の回答のほとんどで基本的に述べられています。

IList<T> defines those methods (not including extension methods)

IList<T> MSDNリンク

  1. 追加
  2. 晴れ
  3. 含む
  4. コピー先
  5. GetEnumerator
  6. の指標
  7. インサート
  8. 削除する
  9. RemoveAt

List<T> では、これらの9つのメソッド(拡張メソッドを含まない)を実装します。さらに、約41のパブリックメソッドがあり、アプリケーションで使用するメソッドを検討する際に重要になります。

List<T> MSDNリンク


4

IListまたはICollectionを定義すると、インターフェイスの他の実装が開かれるためです。

IListまたはICollectionのいずれかで注文のコレクションを定義するIOrderRepositoryが必要になる場合があります。次に、IListまたはICollectionで定義された「ルール」に準拠している限り、注文のリストを提供するさまざまな種類の実装を用意できます。


3

IList <>は、他の投稿者のアドバイスに従ってほとんどの場合推奨されますが、WCF DataContractSerializerで複数のシリアル化/逆シリアル化サイクルを介してIList <>を実行すると、.NET 3.5 sp 1バグがあることに注意してください。

現在、このバグを修正するSPがあります:KB 971030


2

このインターフェースにより、少なくとも予期したメソッドを確実に取得できます。つまり、インターフェースの定義を知っている。インターフェースを継承するクラスによって実装されるすべての抽象メソッド。そのため、追加機能のためにインターフェイスから継承したメソッド以外にいくつかのメソッドを使用して独自の巨大なクラスを作成し、それらが役に立たない場合は、サブクラスへの参照を使用することをお勧めします(この場合はインターフェース)と具象クラスオブジェクトを割り当てます。

追加の利点は、具象クラスのいくつかのメソッドのみをサブスクライブしているため、具象クラスへの変更からコードが安全であり、具象クラスが自分のインターフェイスから継承している限り、これらのメソッドが存在することです。使用しています。そのため、あなたにとっての安全性と、具体的なクラスを変更または機能を追加する具体的な実装を書いているコーダーにとっての自由。


2

この引数は、実装ではなくインターフェイスに対してプログラムするように指示する純粋なOOアプローチの1つを含め、いくつかの角度から見ることができます。このように考えると、IListを使用することは、最初から定義したインターフェイスを渡したり使用したりするのと同じ原則に従います。また、一般的にインターフェイスによって提供されるスケーラビリティと柔軟性の要素も信じています。IList <T>を実装するクラスを拡張または変更する必要がある場合、使用するコードを変更する必要はありません。IListインターフェイスコントラクトが準拠しているものを認識しています。ただし、変更するクラスで具体的な実装とList <T>を使用すると、呼び出しコードも変更する必要が生じる可能性があります。これは、IList <T>に準拠するクラスが、List <T>を使用した具象型では保証されない特定の動作を保証するためです。

また、クラスのList <T>のデフォルト実装を変更するなどの能力を持つことにより、たとえば.Add、.Remove、またはその他のIListメソッドを実装するIList <T>は、開発者に多くの柔軟性と能力を提供します。リスト<T>


0

通常、適切なアプローチは、公開APIでIListを使用し(適切な場合、リストのセマンティクスが必要な場合)、次にListを内部で使用してAPIを実装することです。これにより、クラスを使用するコードを壊すことなく、IListの別の実装に変更できます。

クラス名リストは次の.netフレームワークで変更される可能性がありますが、インターフェースが契約されているため、インターフェースは変更されません。

APIがforeachループなどでのみ使用される場合は、代わりにIEnumerableを公開することを検討してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.