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