私はEnumeratorがどのように機能するのか、そして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*/ }
私がIEnumerable
を使用する場合、私が "sel"をデバッグして調べるとき、それがIEnumerableであることに気づいた、それはいくつかの興味深いメンバーを持っています: "inner"、 "outer"、 "innerKeySelector"そして "outerKeySelector"代議員のようです。 "inner"メンバーには "Animal"インスタンスが含まれていませんが、 "Species"インスタンスが含まれています。これは私にとって非常に奇妙なことです。 「外側」メンバーは「動物」インスタンスを含みません。私は2人の代表がどちらが入って、何がそこから出るのかを決定すると思いますか?
"Distinct"を使用すると、 "inner"には6つの項目が含まれています(2つだけが異なるため、これは正しくありません)が、 "outer"には正しい値が含まれています。繰り返しになりますが、おそらく委任されたメソッドがこれを決定しますが、これはIEnumerableについて私が知っている以上のものです。
最も重要なことに、2つのオプションのうちどちらがパフォーマンス的に最も良いのでしょうか。
.ToList()
による邪悪なリストへの変換?
それとも、列挙子を直接使用しますか?
可能であれば、このIEnumerableの使用方法を説明するリンクや、いくつかのリンクを追加してください。
IEnumerable
は動作を記述しますが、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はそれぞれ単一のSQLクエリに変換され、データベースは関連する行のみを返します。しかし、AllSpotted()
からListを返した場合、データベースが実際に必要なデータよりはるかに多くのデータを返す可能性があるため、実行が遅くなる可能性があり、クライアントでフィルタリングを実行するサイクルを無駄にします。
プログラムでは、最後までクエリをリストに変換するのを遅らせる方が良い場合があるので、LeopardsとHyenasを何度も列挙する場合は、次のようにします。
List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
Claudio BernasconiのTechBlogには、非常に良い記事があります。 IEnumerable、ICollection、IList、およびList を使用する場合
ここではシナリオと機能に関するいくつかの基本的なポイントを示します。
IEnumerable
を実装するクラスでは、foreach
構文を使用できます。
基本的にコレクションの次のアイテムを取得するメソッドがあります。コレクション全体をメモリに入れる必要はなく、その中にいくつの項目があるのかわからないため、foreach
は、次の項目を使い果たすまで取得し続けます。
これは、特定の状況では非常に便利です。たとえば、大規模なデータベーステーブルでは、行の処理を開始する前にすべてのものをメモリにコピーする必要はありません。
List
はIEnumerable
を実装しましたが、メモリ内のコレクション全体を表します。もしあなたがIEnumerable
を持っていて、.ToList()
を呼ぶならば、あなたはメモリの列挙の内容で新しいリストを作成します。
Linq式は列挙型を返します。デフォルトでは、foreach
を使用して繰り返し処理を行うと式が実行されます。 IEnumerable
linqステートメントは、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 ) ...
皮肉なことに、これの複製として閉じられた質問に回答した、1つの重大な違いについて誰も言及しませんでした。
IEnumerableは読み取り専用で、Listは読み取り専用です。
最も重要なことは、Linqを使用してもクエリがすぐに評価されないことです。これは、結果として得られるIEnumerable<T>
をforeach
内で反復することの一部としてのみ実行されます - それがすべての奇妙な代行者がしていることです。
そのため、最初の例では、ToList
を呼び出してクエリ結果をリストに入れることで、クエリをただちに評価します。
2番目の例は、後でクエリを実行するために必要なすべての情報を含むIEnumerable<T>
を返します。
パフォーマンスの面では、答えは それは依存性 です。結果を一度に評価する必要がある場合(たとえば、後で問い合わせている構造を変更している場合、またはIEnumerable<T>
の繰り返しに長時間をかけたくない場合)、リストを使用してください。それ以外の場合はIEnumerable<T>
を使用してください。結果をリストに格納する特別な理由がない限り、デフォルトでは2番目の例のオンデマンド評価を使用する必要があります。
IEnumerableの利点は、(通常はデータベースを使用した)遅延実行です。実際にデータをループするまで、クエリは実行されません。それは必要とされるまで待つクエリです(別名遅延ロード)。
ToListを呼び出すと、クエリが実行されます。つまり、「実体化」されます。
両方に賛否両論があります。 ToListを呼び出すと、いつクエリが実行されるのかという謎を取り除くことができます。あなたがIEnumerableに固執するならば、あなたはそれが実際に必要とされるまでプログラムが少しの仕事もしないという利点を得ます。
私は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() );
あなたがしたいすべてがそれらを列挙である場合は、IEnumerable
を使用しています。
列挙されている元のコレクションを変更することは危険な操作であること、しかし、注意してください - この場合には、あなたが最初にToList
ことになるでしょう。これはIEnumerable
を列挙、メモリ内の各要素の新しいリスト要素を作成し、一度だけ列挙している場合ので、パフォーマンスが低いだろう - しかし、より安全な、時にはList
方法は、(ランダムアクセスで例えば)便利です。
上記のすべての回答に加えて、これが私の2セントです。 List以外にも、ICollection、ArrayListなど、IEnumerableを実装する型は他にも多数あります。したがって、IEnumerableを任意のメソッドのパラメーターとして使用すると、コレクション型を関数に渡すことができます。つまり、特定の実装ではなく抽象化を操作する方法があります。
IEnumerableをListに変換できない場合が多くあります(無限リストや非常に大きなリストなど)。最も明白な例はすべての素数、彼らの詳細を持つfacebookのすべてのユーザー、またはebayの上のすべてのアイテムです。
違いは、 "List"オブジェクトは "今ここで"今すぐ格納されているのに対し、 "IEnumerable"オブジェクトは "一度に1つだけ"動作していることです。だから私がebay上のすべてのアイテムを見ているとしたら、一度に一つずつ小さなコンピューターでも扱えるものになるだろうが、 "。ToList()"は確かに私のコンピューターの大きさに関係なくメモリーを使い果たしてしまう。そのような膨大な量のデータを自分自身で格納し処理することはできません。