11

次の C# コードを検討してください。

IEnumerable numbers = Enumerable.Range(0, 10);
var evens = from num in numbers where num % 2 == 0 select num;

forこれは、 orforeachループをワンライナーとして記述できるようにするための純粋な構文糖ですか? 上記のリスト内包表記をループ構成よりも効率的にするコンパイラーの最適化が隠れてありますか? これは内部でどのように機能しますか?

4

4 に答える 4

14

ジェイソンが言ったように、あなたのコードは次と同等です:

Enumerable.Range(0, 10).Where(n => n % 2 == 0);

ラムダは、すべての要素に対して行われる関数呼び出しに変換されることに注意してください。これはおそらくオーバーヘッドの最大の部分です。この正確なタスクでLINQが約3倍遅いことを示すテストを行いました(モノgmcsバージョン1.2.6.0)

    ループ担当者の 10000000 の時間: 00:00:17.6852560
    10000000 LINQ 担当者の時間: 00:00:59.0574430

    ループ担当者の 1000000 の時間: 00:00:01.7671640
    1000000 LINQ 担当者の時間: 00:00:05.8868350

編集: Gishu は VS2008 とフレームワーク v3.5 SP1 が与えることを報告します:

    1000000 回のループ回数の時間: :00.3724585
    1000000 LINQ 担当者の時間: :00.5119530

LINQ はそこで約 1.4 倍遅くなります。

for ループとリストを LINQ (および内部で使用する構造) と比較します。どちらの方法でも、結果を配列に変換します (LINQ が「怠惰」になるのを強制的に停止するために必要です)。両方のバージョンが繰り返されます:

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

public class Evens
{
    private static readonly int[] numbers = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    private static int MAX_REPS = 1000000;

    public static void Main()
    {
        Stopwatch watch = new Stopwatch();

        watch.Start();
        for(int reps = 0; reps < MAX_REPS; reps++)
        {
            List<int> list = new List<int>(); // This could be optimized with a default size, but we'll skip that.
            for(int i = 0; i < numbers.Length; i++)
            {
                int number = numbers[i];
                if(number % 2 == 0)
                    list.Add(number);
            }
            int[] evensArray = list.ToArray();
        }
        watch.Stop();
        Console.WriteLine("Time for {0} for loop reps: {1}", MAX_REPS, watch.Elapsed);

        watch.Reset();
        watch.Start();
        for(int reps = 0; reps < MAX_REPS; reps++)
        {
            var evens = from num in numbers where num % 2 == 0 select num;
            int[] evensArray = evens.ToArray();
        }
        watch.Stop();
        Console.WriteLine("Time for {0} LINQ reps: {1}", MAX_REPS, watch.Elapsed);
    }
}

同様のタスクでの過去のパフォーマンス テスト (例: LINQ vs Loop - A performance test ) は、これを裏付けています。

于 2009-06-05T03:45:23.670 に答える
5

次の方法でコードをさらに簡素化できます。

var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0);

この形式の利点の 1 つは、evens( foreach(var n in evens) { ... }) が繰り返されるまで、この式の実行が延期されることです。上記のステートメントは、0 から 10 までの偶数を列挙する方法のアイデアをキャプチャするようにコンパイラに指示するだけですが、絶対に必要になるまでそのアイデアを実行しないでください。

于 2009-06-05T03:17:25.343 に答える
4

LINQ は、さまざまな種類のデータに対して異なる動作をします。オブジェクトをフィードしているので、LINQ-to-objects を使用しています。これは、単純な for ループに似たコードに変換されます。

ただし、LINQ はさまざまな種類のデータをサポートしています。たとえば、'numbers' という db テーブルがある場合、LINQ-to-SQL は同じクエリを変換します。

var evens = from num in numbers where num % 2 == 0 select num;

このようにSQLに;

select num from numbers where num % 2 = 0

そしてそれを実行します。内部に「if」ブロックを含む for ループを作成してフィルタリングを実行しないことに注意してください。'where' 句は、データベース サーバーに送信されるクエリを変更します。クエリから実行コードへの変換は、フィードしているデータの種類に固有です。

いいえ、LINQ は単なる for ループのシンタックス シュガーではなく、クエリを「コンパイル」するためのより複雑なシステムです。詳細については、 を検索してLinq Providerください。これらは linq-to-objects や linq-to-xml のようなコンポーネントであり、勇気があれば独自のコンポーネントを作成できます。

于 2009-11-17T14:42:18.417 に答える
1

上記のコードには、foreach ループと同じように機能的に IEnumerable をループする Linq クエリがあります。ただし、コードでは、内部で多くのことが行われています。高性能ループを作成するつもりなら、おそらく foreach の方がはるかに効率的です。Linq は別の目的 (一般化されたデータ アクセス) を目的としています。

IEnumerableインターフェイスは iterator メソッドを公開します。このメソッドは foreach や Linq クエリなどのループ コンストラクトによって継続的に呼び出されます。イテレータは、呼び出されるたびにコレクション内の次のアイテムを返します。

于 2009-06-05T03:20:39.240 に答える