11

考虑以下 C# 代码:

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

这种纯粹的语法糖可以让我写一个forforeach循环作为单行吗?是否有任何编译器优化使上面的列表理解比循环构造更有效?这在引擎盖下是如何工作的?

4

4 回答 4

14

正如杰森所说,您的代码相当于:

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

请注意,lambda 将被转换为对每个元素执行的函数调用。这可能是开销的最大部分。我做了一个测试,这表明 LINQ 在这个确切的任务上慢了大约 3 倍(mono 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 性能测试)证实了这一点。

于 2009-06-05T03:45:23.670 回答
5

您可以通过以下方式进一步简化代码

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

这种形式的一个优点是这个表达式的执行被推迟到evens被迭代(foreach(var n in evens) { ... })。上面的语句只是告诉编译器捕获如何枚举 0 到 10 之间的偶数的想法,但在绝对必要之前不要执行该想法。

于 2009-06-05T03:17:25.343 回答
4

LINQ 对不同类型的数据的工作方式不同。您正在为其提供对象,因此它使用 LINQ-to-objects。这被翻译成类似于简单的 for 循环的代码。

但是 LINQ 支持不同类型的数据。例如,如果您有一个名为“numbers”的数据库表,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

在上面的代码中,您有一个 Linq 查询,它以与 foreach 循环相同的功能方式循环 IEnumerable。但是,在您的代码中,引擎盖下发生了很多事情。如果您打算编写一个高性能循环,那么 foreach 可能效率更高。Linq 旨在用于不同的目的(通用数据访问)。

IEnumerable接口公开了一个迭代器方法,然后由循环构造(如 foreach 或 Linq 查询)连续调用该方法。每次调用迭代器时,它都会返回集合中的下一项。

于 2009-06-05T03:20:39.240 回答