回答:
IEnumerableは、最小限の「反復可能な」機能のみを提供します。シーケンスをトラバースすることはできますが、それだけです。これには欠点があります-たとえば、IEnumerableを使用して要素をカウントしたり、n番目の要素を取得したりすることは非常に非効率的です-しかし、これには利点もあります-たとえば、IEnumerableは、素数のシーケンスのような無限のシーケンスである可能性があります。
配列は、ランダムアクセスの固定サイズのコレクションです(つまり、それにインデックスを付けることができます)。
リストは、ランダムなアクセスが可能な可変サイズのコレクションです(つまり、要素を追加および削除できます)。
IListは、List、BindingList、ObservableCollectionなどのさまざまな具象クラスからリスト機能(カウント、追加、削除、インデクサーアクセス)を抽象化するインターフェイスです。
IEnumerableは、アイテムのコレクション(foreachキーワードなど)による反復を可能にするインターフェースです。
配列は.NET組み込みです。同じタイプのアイテムを保持しますが、サイズは固定されています。x要素を持つ配列を作成すると、それを拡大または縮小することはできません。
IListはリストのインターフェイスを定義し、IEnumerableも実装します。
ListはIListインターフェイスを実装しています。リストの具体的なタイプです。
.NETリストと配列の違いは、リストに要素を追加できることです。これらの要素は、必要なすべてのアイテムを保持できるほど大きくなります。リストはこれを内部的に配列に格納し、配列がすべての要素を保持するのに十分な大きさでなくなると、新しい配列が作成され、項目がコピーされます。
IListと配列はどちらもIEnumerableを実装しています。これがインターフェイスのしくみです。クラスはコントラクトを実装し、同様の方法で動作し、結果として同様に処理できます(クラスがIEnumerableを実装していることを知っているので、方法や理由を知る必要はありません)。インターフェースなどを読むことをお勧めします。
IEnumerableとIListはインターフェイスです。ArrayとListはクラスです。配列はIEnumerableを実装します。Listは、IEnumerableを拡張するIListを実装します。
編集:itowlsonがコメントで言及したように、ArrayはIListも実装します。
IEnumerableコレクションの生成は遅延です。例:
public IEnumerable<int> GetTwoInts()
{
yield return 1;
yield return 2;
}
public void Something()
{
var twoInts = GetTwoInts();
}
メソッドSomethingでは、列挙が繰り返されないため、GetTwoInts()を呼び出しても、実際にはメソッドGetTwoIntsは実行されません。
twoIntsはforeachor呼び出しを行いますtwoInts.ToList()。
IEnumerableなど、多くのクラスで使用される汎用的なインタフェースでArray、ListそしてStringコレクションを誰かの反復処理をさせるためです。基本的に、それがforeach声明を動かすものです。
IList通常、タイプの変数Listをエンドユーザーに公開する方法です。このインターフェイスは、基になるコレクションへのランダムアクセスを許可します。
他の回答を補完するために、そこにあることに注意して、性能差の間には、IList<T>とList<T>foreach文を実行するとき。
これList<T>.GetEnumeratorは、によって返されるイテレータオブジェクトが値型であり、によって返されるイテレータオブジェクトIList<T>.GetEnumeratorが参照型であるため、メモリ割り当てが必要であるためです(c#のリストの値型の列挙子を参照)。
私の意見でIList<T>は、とにかく非常に良いインターフェースではありません。たとえば、呼び出しAddはスローすることができます(配列がIListを実装する理由を参照してください)。カプセル化が必要な場合は、IEnumerable<T>またはを使用することをお勧めしますIReadOnlyList<T>。
他の回答に加えて、LINQを使用するときにEnumerableとList / Arrayの違いを理解すると、パフォーマンスに大きな影響を与える可能性があります。つまり、List / Arrayがクエリの結果である間、Enumerableはクエリビルダーとして認識されます。
EntityFrameworkを使用したLINQ to SQLのコンテキストでは、前者はSQLクエリを構築するだけで、データベースに対して実行したり、メモリにデータをロードしたりせず、後者は反対です。.ToList()ビジネスロジックを実行するためにメモリ内で必要になるまで呼び出しを延期するのはそのためです。
他のコンテキストでは、IEnumerableを返すLINQ式.ToList()は、が呼び出されるまで実行を延期します。以下の例を考えてみましょう:
void Main()
{
var w1 = "AB".AsEnumerable();
Console.WriteLine($"W1: 1");
w1 = w1.Where(W1);
Console.WriteLine($"W1: 2");
w1 = w1.Where(W2);
Console.WriteLine($"W1: 3");
w1.ToList();
Console.WriteLine($"----------");
var w2 = "CD".AsEnumerable();
Console.WriteLine($"W2: 1");
w2 = w2.Where(W1);
Console.WriteLine($"W2: 2");
w2 = w2.ToList();
Console.WriteLine($"W2: 3");
w2 = w2.Where(W2);
Console.WriteLine($"W2: 4");
w2.ToList();
Console.WriteLine($"----------");
}
bool W1(char arg)
{
Console.WriteLine($"W1:{arg}");
return true;
}
bool W2(char arg)
{
Console.WriteLine($"W2:{arg}");
return true;
}
OUTPUT:
W1: 1
W1: 2
W1: 3
W1:A
W2:A
W1:B
W2:B
----------
W2: 1
W2: 2
W1:C
W1:D
W2: 3
W2: 4
W2:C
W2:D
----------
最初の例では、2つ.Where()が「追加」さ.ToList()れ、アイテム「」で呼び出されたときに最後に一緒に実行されます A」が最初にパイプを通過し、次に項目「B」でため、出力に「AABB」が表示され、2番目の例では、これ.Where()は.ToList()、直後に呼び出すと毎回実行されるため、「CD」と「CD」が再度表示され、2回出力されます。したがって、Enumerableがリストまたは配列に変換されるたびに、1つのO(n)がかかります。に、コレクション内のすべてのアイテム反復が回、コレクションが大きい場合にパフォーマンスに影響します。
.ToList()LINQ呼び出しの間に呼び出すこの方法でコードを記述する傾向はありませんが、List/Arrayではなくを返す再利用可能なメソッドにコードを因数分解すると、より頻繁に発生しますIEnumerable。
ただし、これは常にで操作する必要があるという意味ではありませんIEnumerable。towlsonが述べたようにで、などの操作.Count()はコレクションに対して反復を引き起こしますが、リストまたは配列はこの情報を事前に計算しているため、複数回List/Array呼び出す予定がある場合はへの変換がより効率的です.Count()。これは、カウント.Countする.Count()メソッドではなく、リストにプロパティがある理由でもあります。