IEnumerableとList-何を使用するか それらはどのように機能しますか?


676

EnumeratorsとLINQの動作に疑問があります。次の2つの単純な選択を検討してください。

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

または

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

これがより一般的な例のように見えるように、元のオブジェクトの名前を変更しました。クエリ自体はそれほど重要ではありません。私が聞きたいのはこれです:

foreach (Animal animal in sel) { /*do stuff*/ }
  1. 私がを使用するとIEnumerable、 "sel"(この場合はIEnumerable)をデバッグして検査すると、いくつかの興味深いメンバーが含まれていることに気付きました: "inner"、 "outer"、 "innerKeySelector"、 "outerKeySelector"、これらの最後の2つが表示されます代議員になる。「inner」メンバーには「Animal」インスタンスは含まれず、「Species」インスタンスが含まれています。これは私にとって非常に奇妙でした。「外部」メンバーには「動物」インスタンスが含まれています。私は、2人の代表がどちらが出入りするかを決定すると思いますか?

  2. 「Distinct」を使用した場合、「inner」には6つの項目が含まれていることに気付きました(2つだけが異なるため、これは正しくありません)が、「outer」には正しい値が含まれています。繰り返しになりますが、おそらく委任されたメソッドがこれを決定しますが、これはIEnumerableについて私が知っているよりも少し多くなります。

  3. 最も重要なのは、2つのオプションのどちらがパフォーマンスに関して最良であるかです。

経由悪リスト変換.ToList()

または、列挙子を直接使用しますか?

可能であれば、このIEnumerableの使用法について説明するリンクをいくつか紹介するか、スローしてください。

回答:


738

IEnumerableListは動作を記述しますが、Listはその動作の実装です。を使用するIEnumerableと、コンパイラーに作業を後で延期する機会が与えられ、途中で最適化される可能性があります。ToList()を使用する場合は、コンパイラーに結果を即座に再具体化させます。

LINQ式を「スタック」しているときは常にを使用しますIEnumerable。これは、動作を指定するだけで、LINQに評価を延期してプログラムを最適化する機会を与えるためです。LINQは、列挙するまでデータベースにクエリを実行するSQLを生成しないことを覚えていますか?このことを考慮:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

これで、初期サンプル( "AllSpotted")といくつかのフィルターを選択するメソッドができました。だから今これを行うことができます:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

では、Listを使用する方が速いIEnumerableですか?クエリが複数回実行されるのを防ぎたい場合のみ。しかし、全体的に良いのでしょうか?上記では、LeopardsとHyenasはそれぞれ1つのSQLクエリに変換され、データベースは関連する行のみを返します。しかし、からリストを返した場合AllSpotted()、データベースが実際に必要なデータよりもはるかに多くのデータを返す可能性があるため、実行速度が遅くなる可能性があり、クライアントでフィルタリングを行うサイクルが無駄になります。

プログラムでは、クエリのリストへの変換を最後まで延期する方がよい場合があるため、LeopardsとHyenasを複数回列挙する場合は、次のようにします。

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

11
結合の両側を指していると思います。"SELECT * FROM Animals JOIN Species ..."を実行すると、結合の内部は動物であり、外部は種です。
Chris Wenham、2009

10
IEnumerable <T>とIQueryable <T>に関する回答を読んだとき、類似した説明を見たので、IEnumerableはランタイムに自動的にLINQ to Objectsを使用してコレクションをクエリします。したがって、これらの3つのタイプの間で混乱しています。stackoverflow.com/questions/2876616/...
Bronek

4
@Bronekあなたがリンクした答えは真実です。IEnumerable<T>最初の部分の後はLINQ-To-Objectsになります。つまり、Felineを実行するには、すべての斑点を返す必要があります。一方、IQuertable<T>willはクエリを絞り込み、ネコネコ科のみをプルダウンします。
ネイト

21
この答えは非常に誤解を招くものです!@Nateのコメントはその理由を説明しています。IEnumerable <T>を使用している場合、フィルターは何があってもクライアント側で発生します。
Hans

5
はいAllSpotted()は2回実行されます。この回答のより大きな問題は次のステートメントです。「上記の場合、LeopardとHyenaはそれぞれ1つのSQLクエリに変換され、データベースは関連する行のみを返します。」where句がIEnumerable <>で呼び出されており、データベースから既に送信されているオブジェクトをループする方法しかわからないため、これは誤りです。AllSpotted()の戻りとFeline()およびCanine()のパラメーターをIQueryableに作成した場合、フィルターはSQLで発生し、この答えは意味があります。
Hans

178

Claudio BernasconiのTechBlogが書いた非常に優れた記事があります。IEnumerable 、ICollection、IList、Listを使用する場合

ここでは、シナリオと機能に関するいくつかの基本的なポイントを示します。

ここに画像の説明を入力してください ここに画像の説明を入力してください


25
この記事は、コードの一般に公開された部分のみを対象としており、内部の仕組みを対象としていないことに注意してください。 List実装でありIList、そのようのものの上に余分な機能を持っているIList(例えばSortFindInsertRange)。自分でIListover を使用するように強制するとList、これらのメソッドが必要になる可能性があります
Jonathan Twite

4
忘れてはいけないIReadOnlyCollection<T>
Dandré

2
[]ここにもプレーン配列を含めると役立つ場合があります。
jbyrd

眉をひそめるかもしれませんが、このグラフィックと記事を共有していただきありがとうございます
ダニエル

133

実装IEnumerableするクラスでは、foreach構文を使用できます。

基本的には、コレクション内の次のアイテムを取得するメソッドがあります。コレクション全体がメモリにある必要はありません。コレクションにアイテムがいくつあるかわからないforeachため、次のアイテムがなくなるまで取得し続けます。

これは、特定の状況で非常に役立ちます。たとえば、大規模なデータベーステーブルで、行の処理を開始する前に全体をメモリにコピーしたくない場合などです。

現在はをList実装IEnumerableしていますが、メモリ内のコレクション全体を表しています。があり、IEnumerable呼び出す.ToList()場合は、メモリ内の列挙型の内容を使用して新しいリストを作成します。

linq式は列挙を返し、デフォルトでは、を使用して反復すると式が実行されますforeachIEnumerableLINQ文を実行しますが反復する場合foreachがありますが、使用してすぐに反復するためにそれを強制することができます.ToList()

これが私の意味です:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

2
しかし、最初にリストに変換せずに IEnumerableでforeachを実行するとどうなりますか?コレクション全体をメモリにもたらしますか?または、foreachループを反復するときに、要素を1つずつインスタンス化しますか?感謝
Pap

@Pap後者:再度実行され、何も自動的にメモリにキャッシュされません。
キース

重要な差分は1)メモリ内のすべてかどうかのようです。2)IEnumerableを使用するとforeach、リストがインデックスで移動します。今、私は事前にカウント/長さを知りたいのであればthing、IEnumerableは役に立ちませんよね?
Jeb50

@ Jeb50正確ではない-両方ListArray実装IEnumerableIEnumerableメモリコレクションと一度に1つのアイテムを取得する大きなコレクションの両方で機能する最も一般的な分母と考えることができます。電話をかけるIEnumerable.Count()ときは、高速な.Lengthプロパティを呼び出しているか、コレクション全体を調べている可能性がありIEnumerableます。重要なのは、それがわからないということです。それは問題になる可能性がありますが、それを実行するだけの場合foreachは気にしないでください。コードは同じArrayDataReaderそれと同じように動作します。
キース

1
@MFouadKajj使用しているスタックはわかりませんが、ほとんどの場合、各行でリクエストを行っていません。サーバーはクエリを実行し、結果セットの開始点を計算しますが、すべてを取得するわけではありません。小さな結果セットの場合、これは単一のトリップである可能性が高く、大きな結果セットの場合、結果からより多くの行のリクエストを送信しますが、クエリ全体を再実行しません。
キース

97

重大な違いについては誰も言及せず、皮肉にも、これの複製としてクローズされた質問に答えました。

IEnumerableは読み取り専用で、Listはそうではありません。

リストとIEnumerableの実用的な違いを参照してください


フォローアップとして、それはインターフェイスの側面が原因ですか、それともリストの側面が原因ですか?つまり、IListも読み取り専用ですか?
ジェイソンマスターズ

IListは読み取り専用ではありません-docs.microsoft.com/en-us/dotnet/api/…IEnumerableは、作成後に何かを追加または削除するメソッドがないため、読み取り専用です。これは、基本インターフェイスの1つです。 IListが拡張されます(リンクを参照)
CADが

67

理解する最も重要なことは、Linqを使用すると、クエリがすぐに評価されないことです。結果のIEnumerable<T>aの反復処理の一部としてのみ実行されますforeach-これはすべての奇妙なデリゲートが行っていることです。

したがって、最初の例ではToList、クエリの結果を呼び出してリストに入れることにより、クエリをすぐに評価します。
2番目の例は、IEnumerable<T>後でクエリを実行するために必要なすべての情報を含むを返します。

パフォーマンスの面では、答えは、それが依存します。結果を一度に評価する必要がある場合(たとえば、後で照会する構造を変更する場合、またはの反復にIEnumerable<T>時間がかかることを望まない場合)は、リストを使用します。それ以外の場合はを使用しIEnumerable<T>ます。結果をリストに格納する特別な理由がない限り、通常はメモリ使用量が少ないため、デフォルトでは2番目の例のオンデマンド評価を使用する必要があります。


こんにちは::-)に答えてくれてありがとう。これでほとんどすべての疑問が解消されました。Enumerableが「内部」と「外部」に「分割」される理由は何ですか?これは、マウスを介してデバッグ/ブレークモードで要素を検査すると発生します。これはおそらくVisual Studioの貢献ですか?その場で列挙し、列挙型の入力と出力を示していますか?
Axonn

5
それがJoin仕事です-内部と外部は結合の2つの側面です。一般的に、IEnumerables実際のコードとは完全に異なるため、の実際の内容を気にする必要はありません。反復するときだけ実際の出力について心配してください:)
thecoop

40

IEnumerableの利点は、実行が遅延することです(通常はデータベースを使用)。クエリは、実際にデータをループするまで実行されません。これは、必要になるまで待機しているクエリです(別名、遅延読み込み)。

ToListを呼び出すと、クエリが実行されるか、私が言っているように "具体化"されます。

両方の長所と短所があります。ToListを呼び出すと、クエリがいつ実行されるかに関する謎を取り除くことができます。IEnumerableを使用すると、実際に必要になるまでプログラムが機能しないという利点があります。


25

私が1つの日に陥った1つの誤用された概念を共有します。

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

期待される結果

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

実結果

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

説明

他の回答と同様に、結果の評価は、ToListたとえば、呼び出しまたは類似の呼び出しメソッドまで延期されましたToArray

したがって、この場合のコードは次のように書き換えることができます。

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

周りを再生

https://repl.it/E8Ki/0


1
これは、この場合、IEnumerableから取得したlinqメソッド(拡張)が原因です。IEnumerableは、クエリを作成するだけで実行はしません(舞台裏で式ツリーが使用されています)。このようにして、データ(この場合はリスト内のデータ)を変更せずに、そのクエリで多くのことを実行できます。Listメソッドは、準備されたクエリを受け取り、データのソースに対して実行します。
Bronek 2016

2
実際、私はすべての回答を読みましたが、私が投票したのはあなたの回答でした。なぜなら、特にLINQ / SQLについて話すことなく、両者の違いを明確に示しているからです。LINQ / SQLに到達する前に、これらすべてを理解することが不可欠です。賞賛する。
BeemerGuy 2016

それは説明する重要な違いですが、「期待される結果」は実際には期待されていません。それは、デザインというよりも、ある種の落とし穴だと言っています。
ニーム2017年

@ニーム、はい私はどのようにIEnumerable機能するかを理解する前にそれが私の期待でしたが、今は方法を知っているのでそれ以上ではありません;)
amd

15

それらを列挙するだけの場合は、を使用しIEnumerableます。

ただし、列挙されている元のコレクションを変更することは危険な操作であることに注意してください。この場合、ToList最初に行う必要があります。これにより、メモリ内の各要素に新しいリスト要素が作成され、が列挙されるためIEnumerable、一度だけ列挙するとパフォーマンスが低下しますが、安全で、Listメソッドが便利な場合があります(たとえば、ランダムアクセス)。


1
リストの生成がパフォーマンスの低下を意味するかどうかは確かではありません。
Steven Sudit

@スティーブン:確かにセコープとクリスが言ったように、時にはリストを使用する必要があるかもしれません。私の場合、そうではないと結論付けました。@ダレン:「これにより、メモリ内の各要素の新しいリストが作成されます」とはどういう意味ですか?たぶんあなたは「リストエントリ」を意味しましたか?::-)。
Axonn

@Axonnはい、リストのエントリをメンターします。修繕。
Daren Thomas

@Stevenの要素を繰り返し処理する予定の場合IEnumerable、最初にリストを作成する(そしてそれを繰り返し処理する)ことは、要素を2回繰り返し処理することを意味します。したがって、リストでより効率的な操作を実行する必要がない限り、これは実際にはパフォーマンスの低下を意味します。
Daren Thomas

3
@jerhewet:反復されるシーケンスを変更することは決して良い考えではありません。悪いことが起こります。抽象化はリークします。悪魔は私たちの次元に侵入し、大混乱をもたらします。したがって、はい、.ToList()ここで役立ちます;)
ダレントーマス

5

上記のすべての回答に加えて、ここに私の2セントがあります。ICollection、ArrayListなど、IEnumerableを実装するList以外の多くの型があります。そのため、任意のメソッドのパラメーターとしてIEnumerableがある場合、任意のコレクション型を関数に渡すことができます。つまり、特定の実装ではなく、抽象化を操作するメソッドを持つことができます。


1

IEnumerableをListに変換できない場合が多くあります(無限リストや非常に大きなリストなど)。最も明白な例は、すべての素数、facebookのすべてのユーザーとその詳細、またはebayのすべてのアイテムです。

違いは、「リスト」オブジェクトは「ここと今」に格納されるのに対し、「IEnumerable」オブジェクトは「一度に1つだけ」機能することです。したがって、ebayのすべての項目を調べている場合、一度に1つは小さなコンピューターでも処理できるものになりますが、「。ToList()」を使用すると、コンピューターの大きさに関係なく、確実にメモリが不足します。このような膨大な量のデータをコンピュータだけで格納して処理することはできません。

[編集]-言うまでもなく-「これもそれも」ではない。多くの場合、同じクラスでリストとIEnumerableの両方を使用することは理にかなっています。定義上、無限のメモリを必要とするため、世界中のどのコンピュータもすべての素数をリストすることはできません。しかし、がclass PrimeContainerを含むを 簡単に考えることができます。IEnumerable<long> primesこれには明らかな理由でも含まれますSortedList<long> _primes。これまでに計算されたすべての素数。次にチェックされる素数は、既存の素数(平方根まで)に対してのみ実行されます。このようにして、両方を取得します-一度に1つの素数(IEnumerable)と、「これまでの素数」の適切なリスト。これは、(無限)リスト全体のかなり良い近似です。

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