1

LINQ select の一部を使用して、 を返すいくつかのコレクションを作成していますIEnumerable<T>

私の場合は が必要なので、結果をのコンストラクターにList<T>渡して作成します。List<T>

これを行うことのオーバーヘッドについて疑問に思っています。私のコレクションのアイテムは通常数百万にのぼるので、これを考慮する必要があります。

IEnumerable<T>が含まれている場合ValueTypes、それは最悪のパフォーマンスだと思います。

私は正しいですか?Refタイプはどうですか?List<T>.Addどちらにしても、100万回の通話料もかかりますよね?

これを解決する方法はありますか?拡張メソッドを使用してLINQ Selectのようなメソッドを「オーバーロード」できますか?

4

6 に答える 6

6

リストの必要性を避けるのが最善でしょう。IEnumerable<T> を使用して呼び出し元を保持できる場合は、頭の痛い問題を解決できます。

LINQ の ToList() は列挙型を取得し、List<T>(IEnumerable<T>) コンストラクターを使用して、そこから直接新しい List<T> を構築します。これは、リストを自分で作成するのと同じで、パフォーマンスが向上します (ただし、LINQ は null チェックも行います)。

自分で要素を追加する場合は、Add の代わりに AddRange メソッドを使用します。ToList() は AddRange と非常によく似ています (IEnumerable<T> を受け取るコンストラクターを使用しているため)。この場合、通常はこれがパフォーマンスの点で最善の策です。

于 2009-06-22T16:58:18.987 に答える
6

IEnumerable<T>いいえ、代わりに を使用していると仮定すると、要素型が値型であることに特にペナルティはありませんIEnumerable。あなたはボクシングを続けることはありません。

実際に結果のサイズを事前に知っている場合(結果はSelectおそらくそうではありません)、そのサイズのバッファーでリストを作成し、 を使用AddRangeして値を追加することを検討することをお勧めします。そうしないと、リストがいっぱいになるたびに、リストのバッファーのサイズを変更する必要があります。

たとえば、次のようにする代わりに:

Foo[] foo = new Foo[100];
IEnumerable<string> query = foo.Select(foo => foo.Name);
List<string> queryList = new List<string>(query);

あなたがするかもしれません:

Foo[] foo = new Foo[100];
IEnumerable<string> query = foo.Select(x => x.Name);
List<string> queryList = new List<string>(foo.Length);
queryList.AddRange(query);

呼び出しによって元のクエリ ソースと同じ長さのシーケンスが生成されることSelectはわかっていますが、私の知る限り、実行環境にはその情報がありません。

于 2009-06-22T17:02:45.050 に答える
1

他の多くのレスポンダーが、 を にコピーする際のパフォーマンスを向上させる方法について、すでにアイデアを提供してIEnumerable<T>List<T>ます。

ただし、説明した内容に基づいて、結果を処理する必要があり、完了したらリストを削除するという事実 (これは、中間結果が面白くないことを意味すると思います) - 検討することをお勧めします。を具体化する必要があるかどうかList<T>

List<T>を作成してそのリストの内容を操作するのではなくIEnumerable<T>、同じ処理ロジックを実行する遅延拡張メソッドを作成することを検討してください。私はこれを自分で多くのケースで行ってきましたが[yield return][1]、コンパイラでサポートされている構文を使用する場合、C# でそのようなロジックを記述することはそれほど悪くありません。

このアプローチは、結果の各項目にアクセスしてそこから情報を収集することだけを目的としている場合にうまく機能します。多くの場合、コレクション内の各要素にオンデマンドでアクセスし、処理を行ってから次に進む必要があります。このアプローチは、通常、コレクションのコピーを作成して反復処理するよりもスケーラブルでパフォーマンスが高くなります。

さて、このアドバイスは他の理由でうまくいかないかもしれませんが、非常に大きなリストを具体化する最も効率的な方法を見つける代わりに検討する価値があります。

于 2009-06-22T17:32:15.980 に答える
1

List コンストラクターに IEnumerable を渡さないでください。IEnumerable には ToList() メソッドがあり、それよりも悪いことはありませんが、より優れた構文 (IMHO) を備えています。

とはいえ、それはあなたの質問への答えを「依存する」に変更するだけです-特に、IEnumerableが実際に舞台裏にあるものに依存します。それがたまたま既に List である場合、ToListは実質的に解放されます。もちろん、それが別の型である場合よりもはるかに高速になります。まだ超高速ではありません。

もちろん、これを解決する最善の方法は、List ではなく IEnumerable で処理を行う方法を見つけようとすることです。それは不可能かもしれません。


編集: コメントの一部の人々は、リストで呼び出されたときに ToList() がそうでない場合よりも実際に高速になるかどうか、および ToList() がリストコンストラクターよりも高速になるかどうかについて議論しています。この時点で、憶測は無意味になるので、ここにいくつかのコードを示します。

using System;
using System.Linq;
using System.Collections.Generic;

public static class ToListTest
{
    public static int Main(string[] args)
    {
        List<int> intlist = new List<int>();
        for (int i = 0; i < 1000000; i++)
            intlist.Add(i);

        IEnumerable<int> intenum = intlist;

        for (int i = 0; i < 1000; i++)
        {
            List<int> foo = intenum.ToList();
        }

        return 0;
    }
}

実際にはリストである IEnumerable を使用してこのコードを実行すると、LinkedList または Stack に置き換えた場合よりも約 6 ~ 10 倍高速になります (Mono 1.2.6 を使用するポーキー 2.4 GHz P4 で)。おそらくこれは、ToList() と LinkedList または Stack の列挙型の特定の実装との間の不運な相互作用が原因である可能性がありますが、少なくともポイントは残ります: 速度は IEnumerable の基になる型に依存します。とはいえ、リストをソースとして使用しても、1000 回の ToList() 呼び出しを行うのに 6 秒かかるため、無料とは言えません。

次の問題は、ToList() が List コンストラクターよりもインテリジェントかどうかです。それに対する答えはノーです。List コンストラクターは ToList() と同じくらい高速です。後から考えると、Jon Skeet の推論は理にかなっています。ToList() が拡張メソッドであることを忘れていました。私はまだ構文的に ToList() を (かなり) 好みますが、それを使用するパフォーマンス上の理由はありません。

したがって、短いバージョンでは、最善の答えは依然として「回避できる場合はリストに変換しないでください」です。それを除けば、実際のパフォーマンスは IEnumerable が実際に何であるかに大きく依存しますが、氷河とは対照的に、せいぜい遅くなります。これを反映するために、元の回答を修正しました。

于 2009-06-22T16:59:31.090 に答える
1

一般的に言えば、返されるメソッドIEnumerableは、アイテムが実際に必要になる前にアイテムを評価する必要はありません。したがって、理論的には、アイテムを返すときに、IEnumerableその時点でアイテムが存在する必要はありません。

したがって、リストを作成するということは、項目を評価し、それらを取得して、メモリ内のどこかに配置する必要があることを意味します (少なくともそれらの参照)。リストが本当に必要な場合は、これについてできることは何もありません。

于 2009-06-22T17:04:58.027 に答える