C# 列表推导式 = 纯语法糖?

问题描述 投票:0回答:4

考虑以下 C# 代码:

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

这是纯粹的语法糖吗?允许我编写一个

for
foreach
循环作为单行代码吗?是否有任何编译器优化在幕后使上面的列表理解比循环构造更有效?这在幕后是如何工作的?

c# linq optimization list-comprehension
4个回答
14
投票

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

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 - 性能测试)证实了这一点。


5
投票

您可以进一步简化代码

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

这种形式的一个优点是该表达式的执行被推迟到

evens
被迭代 (
foreach(var n in evens) { ... }
) 为止。上面的语句只是告诉编译器捕获如何枚举 0 到 10 之间的偶数的想法,但除非绝对必要,否则不要执行该想法。


4
投票

LINQ 对于不同类型的数据的工作方式有所不同。您向它提供对象,因此它使用 LINQ 到对象。它被翻译成类似于简单的 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 之类的组件,如果您有勇气,可以编写自己的组件。


1
投票

在上面的代码中,您有一个 Linq 查询,它在功能上与 foreach 循环相同的方式循环 IEnumerable。 然而,在您的代码中,幕后发生了很多事情。 如果您打算编写高性能循环,则 foreach 可能会更有效。 Linq 旨在用于不同的目的(通用数据访问)。

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

© www.soinside.com 2019 - 2024. All rights reserved.