回答:
他のユーザーが使用するライブラリを介してクラスを公開する場合、通常、具体的な実装ではなく、インターフェイスを介してクラスを公開する必要があります。これは、後で別の具象クラスを使用するようにクラスの実装を変更する場合に役立ちます。その場合、インターフェースは変更されないため、ライブラリのユーザーはコードを更新する必要はありません。
内部で使用しているだけであれば、それほど気にする必要はなく、使用してもかまいませんList<T>
。
それほど一般的ではない答えは、プログラマーがソフトウェアを世界中で再利用するふりをするようなものであり、プロジェクトの大部分は実際には少数の人々によって維持され、インターフェイスに関連した適切なサウンドバイトがどれほどであるかはわかりません。あなた自身。
建築宇宙飛行士。.NETフレームワークに既にあるものに何かを追加する独自のIListを作成する可能性は非常に低いため、理論的には「ベストプラクティス」のために用意されたゼリーです。
インタビューでどちらを使用するかを尋ねられているのは明らかですが、IListと言って微笑みます。どちらも、自分がとても賢いことを喜んでいます。または、一般向けAPIのIListの場合。うまくいけば、あなたは私のポイントを得ます。
IEnumerable<string>
。関数の内部ではList<string>
、内部バッキングストアにを使用してコレクションを生成できますが、呼び出し元にそのコンテンツを列挙するだけで、追加や削除はさせないようにします。インターフェイスをパラメータとして受け入れると、「文字列のコレクションが必要ですが、変更しないでください」という同様のメッセージが送信されます。
インターフェイスは約束(または契約)です。
それは常に約束のとおりです- 小さいほど良いです。
IList
約束もそうです。ここでどちらが小さく、より良い約束ですか?これは論理的ではありません。
List<T>
ので、AddRange
インターフェイスで定義されていません。
IList<T>
守れない約束をする。たとえば、たまたま読み取り専用のAdd
場合にスローできるメソッドがありますIList<T>
。List<T>
一方、常に書き込み可能であるため、常に約束が守られます。また、.NET 4.6以降ではIReadOnlyCollection<T>
、がIReadOnlyList<T>
あり、ほとんどの場合がよりも適していますIList<T>
。そして、それらは共変です。避けてくださいIList<T>
!
IList<T>
。誰かが実装IReadOnlyList<T>
してすべてのメソッドに例外をスローさせることもできます。これはアヒルのタイピングとは正反対のことです。アヒルと呼んでいますが、アヒルのようにはいられません。コンパイラがそれを強制できない場合でも、それは契約の一部です。私は100%ということに同意しますIReadOnlyList<T>
かIReadOnlyCollection<T>
かかわらず、読み取り専用アクセスのために使用すべきです。
一部の人々は「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定義を参照してください。
IsReadOnly
ですtrue
のためにIList
、あなたはまだ、現在のメンバーを変更することができます!おそらく、そのプロパティに名前を付ける必要がありIsFixedSize
ますか?
Array
として渡すことでテストされたIList
ため、現在のメンバーを変更できます)
IReadOnlyCollection<T>
、がIReadOnlyList<T>
あり、ほとんどの場合IList<T>
、の危険なレイジーセマンティクスはありませんが、ほとんどの場合より適していますIEnumerable<T>
。そして、それらは共変です。避けてくださいIList<T>
!
List<T>
はの特定の実装でありIList<T>
、T[]
整数インデックスを使用して線形配列と同じ方法でアドレス指定できるコンテナです。あなたが指定した場合IList<T>
、メソッドの引数の型として、あなただけがコンテナの特定の機能を必要とすることを指定します。
たとえば、インターフェース仕様は、特定のデータ構造の使用を強制しません。の実装はList<T>
、線形配列として要素にアクセス、削除、および追加する場合と同じパフォーマンスになります。ただし、リンクリストに裏打ちされた実装を想像できます。この場合、末尾に要素を追加する方がコストは低くなりますが(一定時間)、ランダムアクセスの方がはるかに高価です。(.NET LinkedList<T>
はを実装していませんIList<T>
。)
この例は、引数リストでインターフェースではなく実装を指定する必要がある場合があることも示しています。この例では、特定のアクセスパフォーマンス特性が必要な場合はいつでも。これは通常、コンテナの特定の実装で保証されています(List<T>
ドキュメント:「IList<T>
必要に応じてサイズが動的に増加する配列を使用して汎用インターフェースを実装します。」)。
さらに、必要な最小限の機能を公開することを検討してください。例えば。リストの内容を変更する必要がない場合は、拡張されるの使用を検討する必要がIEnumerable<T>
ありIList<T>
ます。
LinkedList<T>
撮る対List<T>
対IList<T>
すべて呼び出されるコードで必要とされる性能保証の何かを伝えます。
質問を少し変えて、具体的な実装よりもインターフェースを使用する理由を正当化するのではなく、インターフェースではなく具象実装を使用する理由を正当化しようとします。正当化できない場合は、インターフェースを使用してください。
TDDとOOPの原則は、一般に、実装ではなくインターフェースへのプログラミングです。
この特定のケースでは、基本的には言語の構成要素について話しているのであり、カスタムの構成要素については関係ありませんが、たとえば、Listが必要なものをサポートしていないことがわかりました。アプリの残りの部分でIListを使用していた場合は、Listを独自のカスタムクラスで拡張し、リファクタリングを行わずにそれを渡すことができます。
これを行うためのコストは最小限です。後で頭痛の種を保存しないのはなぜですか?それがインターフェースの原則です。
これらのリストとIListの質問(または回答)のいずれも署名の違いについて言及していないことに驚いています。(これが私がSOでこの質問を検索した理由です!)
したがって、少なくとも.NET 4.5(2015年頃)以降、IListにないListに含まれるメソッドは次のとおりです
Reverse
そして、ToArray
のIEnumerable <T>インターフェイスの拡張方法のIList <T>導出するからです。
List
の他の実装では意味がないためですIList
。
実装でインターフェースを使用する最も重要なケースは、APIのパラメーターです。APIがListパラメータを受け取る場合、それを使用する人は誰でもListを使用する必要があります。パラメータタイプがIListの場合、呼び出し元ははるかに自由になり、聞いたことのないクラスを使用できます。これは、コードの記述時に存在していなかった可能性もあります。
何.NET 5.0置き換える場合System.Collections.Generic.List<T>
にSystem.Collection.Generics.LinearList<T>
。.NETは常に名前を所有しますList<T>
が、それIList<T>
が契約であることを保証します。したがって、私たち(少なくとも私)は、誰かの名前(この場合は.NETですが)を使用して後で問題が発生することは想定されていません。
を使用する場合IList<T>
、呼び出し元は常に動作することが保証されており、実装者は基礎となるコレクションを任意の代替の具体的な実装に自由に変更できます。IList
すべての概念は、具体的な実装よりもインターフェースを使用する理由に関する上記の回答のほとんどで基本的に述べられています。
IList<T> defines those methods (not including extension methods)
IList<T>
MSDNリンク
List<T>
では、これらの9つのメソッド(拡張メソッドを含まない)を実装します。さらに、約41のパブリックメソッドがあり、アプリケーションで使用するメソッドを検討する際に重要になります。
List<T>
MSDNリンク
IListまたはICollectionを定義すると、インターフェイスの他の実装が開かれるためです。
IListまたはICollectionのいずれかで注文のコレクションを定義するIOrderRepositoryが必要になる場合があります。次に、IListまたはICollectionで定義された「ルール」に準拠している限り、注文のリストを提供するさまざまな種類の実装を用意できます。
このインターフェースにより、少なくとも予期したメソッドを確実に取得できます。つまり、インターフェースの定義を知っている。インターフェースを継承するクラスによって実装されるすべての抽象メソッド。そのため、追加機能のためにインターフェイスから継承したメソッド以外にいくつかのメソッドを使用して独自の巨大なクラスを作成し、それらが役に立たない場合は、サブクラスへの参照を使用することをお勧めします(この場合はインターフェース)と具象クラスオブジェクトを割り当てます。
追加の利点は、具象クラスのいくつかのメソッドのみをサブスクライブしているため、具象クラスへの変更からコードが安全であり、具象クラスが自分のインターフェイスから継承している限り、これらのメソッドが存在することです。使用しています。そのため、あなたにとっての安全性と、具体的なクラスを変更または機能を追加する具体的な実装を書いているコーダーにとっての自由。
この引数は、実装ではなくインターフェイスに対してプログラムするように指示する純粋なOOアプローチの1つを含め、いくつかの角度から見ることができます。このように考えると、IListを使用することは、最初から定義したインターフェイスを渡したり使用したりするのと同じ原則に従います。また、一般的にインターフェイスによって提供されるスケーラビリティと柔軟性の要素も信じています。IList <T>を実装するクラスを拡張または変更する必要がある場合、使用するコードを変更する必要はありません。IListインターフェイスコントラクトが準拠しているものを認識しています。ただし、変更するクラスで具体的な実装とList <T>を使用すると、呼び出しコードも変更する必要が生じる可能性があります。これは、IList <T>に準拠するクラスが、List <T>を使用した具象型では保証されない特定の動作を保証するためです。
また、クラスのList <T>のデフォルト実装を変更するなどの能力を持つことにより、たとえば.Add、.Remove、またはその他のIListメソッドを実装するIList <T>は、開発者に多くの柔軟性と能力を提供します。リスト<T>