考虑以下 C# 代码:
IEnumerable numbers = Enumerable.Range(0, 10);
var evens = from num in numbers where num % 2 == 0 select num;
这是纯粹的语法糖吗?允许我编写一个
for
或 foreach
循环作为单行代码吗?是否有任何编译器优化在幕后使上面的列表理解比循环构造更有效?这在幕后是如何工作的?
正如 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 - 性能测试)证实了这一点。
您可以进一步简化代码
var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0);
这种形式的一个优点是该表达式的执行被推迟到
evens
被迭代 (foreach(var n in evens) { ... }
) 为止。上面的语句只是告诉编译器捕获如何枚举 0 到 10 之间的偶数的想法,但除非绝对必要,否则不要执行该想法。
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 之类的组件,如果您有勇气,可以编写自己的组件。
在上面的代码中,您有一个 Linq 查询,它在功能上与 foreach 循环相同的方式循环 IEnumerable。 然而,在您的代码中,幕后发生了很多事情。 如果您打算编写高性能循环,则 foreach 可能会更有效。 Linq 旨在用于不同的目的(通用数据访问)。
IEnumerable 接口公开了一个迭代器方法,然后由循环构造(例如 foreach 或 Linq 查询)连续调用该方法。 迭代器每次被调用时都会返回集合中的下一个项目。