質問
C# で List ではなく IList を使用する理由を説明できる人はいますか?
関連する質問: なぜ暴露するのは悪いことだと考えられているのか List<T>
解決
他のユーザーが使用するライブラリを介してクラスを公開する場合、一般的には、具体的な実装ではなくインターフェイスを介して公開する必要があります。これは、後でクラスの実装を変更して別の具体的なクラスを使用することにした場合に役立ちます。その場合、インターフェイスが変更されないため、ライブラリのユーザーがコードを更新する必要はありません。
内部で使用しているだけであれば、それほど気にする必要はありません。また、 List&lt; T&gt;
を使用しても大丈夫です。
他のヒント
あまり人気のない答えは、ソフトウェアが世界中で再利用されるふりをするプログラマのようなものです。実際、プロジェクトの大部分は少数の人々によって維持されますが、インターフェース関連の素晴らしいサウンドビットがあれば、 「だまされている。
建築宇宙飛行士。 .NETフレームワークに既に存在するものに何かを追加する独自のIListを作成する可能性は非常に低いため、「ベストプラクティス」のために予約されている理論的なゼリートッツです。
明らかに、インタビューでどちらを使用するか尋ねられたら、IListと笑みを浮かべてください。または、公開されているAPIであるIListの場合。うまくいけば、私のポイントが得られます。
インターフェースは約束です(または契約)。
常に約束どおり-小さいほど良い。
List&lt; T&gt;
&quot;ではなく、常に IList&lt; T&gt;
を使用すると言う人もいます。
メソッドシグネチャを void Foo(List&lt; T&gt; input)
から void Foo(IList&lt; T&gt; input)
に変更してほしい。
これらの人々は間違っています。
それはそれよりも微妙です。ライブラリのパブリックインターフェイスの一部として IList&lt; T&gt;
を返す場合は、将来カスタムリストを作成するための興味深いオプションを残してください。そのオプションは必要ないかもしれませんが、それは議論です。具体的な型ではなく、インターフェイスを返すためのすべての引数だと思います。言及する価値はありますが、この場合は重大な欠陥があります。
マイナーな反論として、すべての呼び出し元がとにかく List&lt; T&gt;
を必要とし、呼び出しコードに .ToList()
さらに重要なことですが、パラメータとしてIListを受け入れる場合は、 IList&lt; T&gt;
および List&lt; T&gt ;
は同じように動作しません。名前が類似しているにもかかわらず、インターフェースを共有しているにもかかわらず、それらは同じ契約を公開しませんではありません。
このメソッドがあるとします:
public Foo(List<int> a)
{
a.Add(someNumber);
}
役に立つ同僚&quot;リファクタリング&quot; IList&lt; int&gt;
を受け入れるメソッド。
int []
は IList&lt; int&gt;
を実装していますが、サイズは固定されているため、コードは破損しています。 ICollection&lt; T&gt;
( IList&lt; T&gt;
のベース)のコントラクトでは、試みる前にそれを使用して IsReadOnly
フラグをチェックするコードが必要です。コレクションにアイテムを追加または削除します。 List&lt; T&gt;
のコントラクトはそうではありません。
Liskov Substitution Principle(簡体字)は、派生型を基本型の代わりに使用でき、追加の前提条件や事後条件はないことを示しています。
これは、リスコフ置換の原則を破っているように感じます。
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&lt; T&gt; / ICollection&lt; T&gt;を使用したことです。違う。 ICollection&lt; T&gt;を使用する場合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 []
のような読み取り専用リストが渡された場合、正確に何をするつもりでしたか?
IList&lt; T&gt;
/ ICollection&lt; T&gt;
正しく List&lt; T&gt;
を使用するコードに置き換えることができます。 IList&lt; T&gt;
/ ICollection&lt; T&gt;
を List&lt; T&gt; <を使用するコードに置き換えることができることを保証することはできません / code>。
具体的な型の代わりに抽象化を使用するという多くの議論の中で、単一の責任原則/インターフェース分離原則に魅力があります-可能な限り狭いインターフェースに依存します。ほとんどの場合、 List&lt; T&gt;
を使用していて、代わりに幅の狭いインターフェイスを使用できると思う場合は、 IEnumerable&lt; T&gt;
を選択してください。多くの場合、アイテムを追加する必要がない場合に適しています。コレクションに追加する必要がある場合は、具象タイプ List&lt; T&gt;
を使用します。
私にとっては たとえば、インターフェース仕様は、使用される特定のデータ構造を強制しません。 この例は、引数リストでインターフェースではなく実装を指定する必要がある場合もあることを示しています。この例では、特定のアクセスパフォーマンス特性が必要なときはいつでも。これは通常、コンテナの特定の実装に対して保証されます( さらに、必要最小限の機能を公開することを検討することもできます。例えば。リストの内容を変更する必要がない場合は、おそらく IList&lt; T&gt;
(および ICollection&lt; T&gt;
)は.NETフレームワークの最悪の部分です。 IsReadOnly
は、最小の驚きの原則に違反しています。アイテムの追加、挿入、または削除を許可しない Array
などのクラスは、Add、Insert、およびRemoveメソッドとのインターフェースを実装しないでください。 (
List&lt; T&gt;
は、 IList&lt; T&gt;
の特定の実装であり、線形配列 Tと同じ方法でアドレス指定できるコンテナです。 []
整数インデックスを使用します。メソッドの引数の型として IList&lt; T&gt;
を指定する場合、コンテナの特定の機能が必要であることのみを指定します。 List&lt; T&gt;
の実装は、線形配列として要素にアクセス、削除、および追加するのと同じパフォーマンスになります。ただし、代わりにリンクリストに裏付けされた実装を想像できます。その場合、要素を最後に追加する方が安価(一定時間)ですが、ランダムアクセスははるかに高価です。 (.NET LinkedList&lt; T&gt;
は IList&lt; T&gt; を実装していないことに注意してください。) List&lt; T&gt;
ドキュメント:&quot;サイズが動的に変更される配列を使用して、 IList&lt; T&gt;
汎用インターフェイスを実装します必要に応じて増加します。&quot;)。 IList&lt; T&gt;
が拡張する IEnumerable&lt; T&gt;
の使用を検討する必要があります。
私は質問を少し変えて、具体的な実装ではなくインターフェイスを使用する必要がある理由を正当化するのではなく、インターフェイスではなく具体的な実装を使用する理由を正当化するように努めます。正当化できない場合は、インターフェイスを使用してください。
IList&lt; T&gt;はインターフェイスであるため、別のクラスを継承し、IList&lt; T&gt;を実装できます。リストを継承しながら&lt; T&gt;そうすることができません。
たとえば、クラスAがあり、クラスBがそれを継承する場合、List&lt; T&gt;を使用できません
class A : B, IList<T> { ... }
TDDおよびOOPの原則は、一般に実装ではなくインターフェイスへのプログラミングです。
この特定のケースでは、本質的に言語の構成について話しているので、通常は重要ではありませんが、たとえば、Listは必要なものをサポートしていません。アプリの残りの部分でIListを使用した場合は、独自のカスタムクラスでリストを拡張し、リファクタリングせずにそれを渡すことができます。
これを行うためのコストは最小限に抑えられていますが、後で頭痛を軽減してみませんか?インターフェースの原則がすべてです。
public void Foo(IList<Bar> list)
{
// Do Something with the list here.
}
この場合、IList&lt; Bar&gt;を実装するクラスを渡すことができます。インタフェース。 List&lt; Bar&gt;を使用した場合代わりに、リスト&lt;バー&gt;のみインスタンスを渡すことができます。
IList&lt; Bar&gt; wayはList&lt; Bar&gt;よりも疎結合です。方法。
実装ではなくインターフェースを使用する最も重要なケースは、APIのパラメーターです。 APIがListパラメーターを受け取る場合、それを使用するユーザーはListを使用する必要があります。パラメータタイプがIListの場合、呼び出し元はより自由になり、聞いたこともないクラスを使用できます。これは、コードが記述されたときにも存在しなかった可能性があります。
これらのリストとIListの質問(または回答)のいずれにも署名の違いが記載されていないことは驚くべきことです。 (SOでこの質問を検索したのはそのためです!)
したがって、少なくとも.NET 4.5(2015年頃)の時点で、IListにはないListに含まれるメソッドは次のとおりです
- AddRange
- AsReadOnly
- BinarySearch
- 容量
- ConvertAll
- 存在する
- 検索
- FindAll
- FindIndex
- FindLast
- FindLastIndex
- ForEach
- GetRange
- InsertRange
- LastIndexOf
- RemoveAll
- RemoveRange
- リバース
- 並べ替え
- ToArray
- TrimExcess
- TrueForAll
.NET 5.0が System.Collections.Generic.List&lt; T&gt;
を System.Collection.Generics.LinearList&lt; T&gt;
に置き換えた場合。 .NETは常に List&lt; T&gt;
という名前を所有していますが、 IList&lt; T&gt;
が契約であることを保証します。だから私たち(少なくとも私)は誰かの名前を使うべきではなく(この場合は.NETですが)後でトラブルに巻き込まれることになっています。
IList&lt; T&gt;
を使用する場合、呼び出し元は常に動作することを保証されており、実装者は基になるコレクションを IListの具体的な代替実装に自由に変更できますコード>
すべての概念は、具体的な実装ではなくインターフェイスを使用する理由に関する上記の回答のほとんどに基本的に記載されています。
IList<T> defines those methods (not including extension methods)
IList&lt; T&gt;
MSDNリンク
- 追加
- クリア
- 含む
- CopyTo
- GetEnumerator
- IndexOf
- 挿入
- 削除
- RemoveAt
List&lt; T&gt;
は、これらの9つのメソッド(拡張メソッドを除く)を実装します。さらに、約41のパブリックメソッドがあり、アプリケーションでどのメソッドを使用するかを検討します。
リスト&lt; T&gt;
MSDNリンク
IListまたはICollectionを定義すると、インターフェイスの他の実装が開かれるためです。
IListまたはICollectionで注文のコレクションを定義するIOrderRepositoryが必要な場合があります。その後、「ルール」に準拠している限り、さまざまな種類の実装で注文のリストを提供できます。 IListまたはICollectionによって定義されます。
このインターフェースにより、少なくとも期待するメソッドを取得できます 。インターフェースの定義を認識すること。インターフェイスを継承するクラスによって実装されるすべての抽象メソッド。したがって、ある機能を追加するためにインターフェースから継承したメソッドに加えて、いくつかのメソッドを使用して独自の巨大なクラスを作成し、それらが役に立たない場合は、サブクラスへの参照を使用することをお勧めします(この場合インターフェース)、具象クラスオブジェクトを割り当てます。
追加の利点は、具象クラスのメソッドのごく一部にサブスクライブしているため、具象クラスへの変更に対してコードが安全であることです。具象クラスは、具象クラスが使用しているインターフェイス。そのため、あなたにとっての安全性と、具象クラスの機能を変更または追加するための具象実装を書いているコーダーに対する自由があります。
IList&lt;&gt;他のポスターのアドバイスによると、ほとんど常に望ましいですが、 IList&lt;&gt;を実行すると、.NET 3.5 sp 1 にバグがあります。 WCF DataContractSerializerを使用したシリアル化/逆シリアル化の複数のサイクルを通じて。
このバグを修正するSPがあります: KB 971030
この引数は、実装ではなくインターフェイスに対してプログラミングするという純粋にオブジェクト指向のアプローチを含む、いくつかの角度から見ることができます。この考え方により、IListの使用は、最初から定義したインターフェイスを渡したり使用したりするのと同じ原則に従います。また、一般的なインターフェイスによって提供されるスケーラビリティと柔軟性の要因も信じています。 IList&lt; T&gt;を実装しているクラス拡張または変更する必要があり、消費するコードを変更する必要はありません。 IListインターフェイスコントラクトが何を遵守しているかを知っています。ただし、具体的な実装とList&lt; T&gt;変更するクラスでは、呼び出しコードも変更する必要があります。これは、クラスがIList&lt; T&gt;に準拠しているためです。 List&lt; T&gt;を使用した具体的な型では保証されない特定の動作を保証します。
また、List&lt; T&gt;のデフォルトの実装を変更するようなこともできます。 IList&lt; T&gt;を実装するクラスたとえば、.Add、.Remove、またはその他のIListメソッドは、開発者に柔軟性とパワーの lot を提供します。それ以外の場合はList&lt; T&gt;
によって事前定義されます通常、適切なアプローチは、公開されているAPIでIListを使用し(適切な場合、およびリストのセマンティクスが必要な場合)、次に内部でリストしてAPIを実装することです。 &nbsp;これにより、クラスを使用するコードを壊すことなく、IListの別の実装に変更できます。
クラス名リストは、次の.netフレームワークで変更される可能性がありますが、インターフェイスはコントラクトであるため、インターフェイスは変更されません。
APIがforeachループなどでのみ使用される場合、代わりにIEnumerableを公開することを検討する必要があることに注意してください。