C#List Comprehensions =纯粹的句法糖?

时间:2009-06-05 03:12:22

标签: c# linq optimization compiler-construction list-comprehension

考虑以下C#代码:

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

这个纯粹的语法糖是否允许我将forforeach循环写为单行?是否有任何编译器优化使得上面的列表理解比循环结构更有效?这是如何工作的?

4 个答案:

答案 0 :(得分:14)

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

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

请注意,lambda将转换为函数调用,该函数调用是针对每个元素完成的。这可能是开销的最大部分。我做了一个测试,这表明LINQ在这个确切的任务上慢了3倍(mono gmcs version 1.2.6.0)

    Time for 10000000 for loop reps: 00:00:17.6852560
    Time for 10000000 LINQ reps: 00:00:59.0574430

    Time for 1000000 for loop reps: 00:00:01.7671640
    Time for 1000000 LINQ reps: 00:00:05.8868350
编辑:Gishu报告说VS2008和框架v3.5 SP1给出了:

    Time for 1000000 loop reps: :00.3724585 
    Time for 1000000 LINQ reps: :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)证实了这一点。

答案 1 :(得分:5)

您可以通过

进一步简化代码
var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0);

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

答案 2 :(得分:4)

LINQ对不同类型的数据的工作方式不同。你正在为它提供物体,所以它正在使用LINQ到物体。它被转换为类似于简单的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这样的组件,如果你有勇气,你可以自己编写。

答案 3 :(得分:1)

在上面的代码中,您有一个Linq查询,它在功能上以与foreach循环相同的方式循环遍历IEnumerable。但是,在您的代码中,有很多内容正在进行中。如果您打算编写一个高性能循环,那么foreach可能会更有效率。 Linq旨在用于不同的目的(通用数据访问)。

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