C#中使用foreach循环的linq排序和其他语句,性能上有差异吗?

6

我是一个与实体框架、集合以及使用where条件和order by操作进行大量编程的人。我一直在想这个问题,但一直没有弄清楚。

假设我有以下两段代码;

示例1:

// An unsorted string array.
string[] letters = { "d", "c", "a", "b" };
// Use LINQ query syntax to sort the array alphabetically.
var sorted = from letter in letters
         orderby letter
         select letter;

// Loop with the foreach keyword.
foreach (string value in sorted)
{
    Console.WriteLine(value);
}

例子2:

// An unsorted string array.
string[] letters = { "d", "c", "a", "b" };

// Loop with the foreach keyword.
foreach (string val in letters.OrderBy(l => l))
{
    console.writeline(val)
}

第一个示例在结果集上先进行排序,然后在我们遍历它的时候对集合进行迭代,而第二个示例在我们要进行迭代时对其进行排序。现在我一直在想的真正问题是..(如果有的话)性能差异是什么?其中一种方法比另一种更好吗?同样,对于where条件(简单和复杂的连接条件),是否存在明显的差异?


1
检查IL,这些应该编译成非常相似,如果不是完全相同的代码。你的第一个示例仍然是延迟操作,所以只有在迭代时才会运行。第二个示例是第一个示例的扩展方法等效。至于进一步的差异,您需要进行分析才能看到,但总体而言,性能不应成为针对数据集工作表现的关注点。如果性能是一个问题,请确定代码的热点,然后审查您对Linq(以及更进一步的迭代器)的使用。大多数情况下,提前做这些并不值得。 - Adam Houldsworth
没有明显的区别,因为它们几乎是相同的。前者被编译成方法语法,无论您是否将查询分配给变量,都不会真正影响性能。 - Tim Schmelter
1
它是一样的 - OP对LINQ延迟执行毫不知情。这意味着在foreach中排序发生在两个时间点上。 - TomTom
3个回答

5

两者之间没有区别。

代码 -

static void Main(string[] args)
{
    string[] letters = { "d", "c", "a", "b" };
    // Use LINQ query syntax to sort the array alphabetically.
    var sorted = from letter in letters
                    orderby letter
                    select letter;

    // Loop with the foreach keyword.
    foreach (string value in sorted)
    {
        Console.WriteLine(value);
    }

    foreach (string val in letters.OrderBy(letter => letter))
    {
        Console.WriteLine(val);
    }
}

生成的代码 -

private static void Main(string[] args)
{
  string[] strArray1 = new string[4]
  {
    "d",
    "c",
    "a",
    "b"
  };
  string[] strArray2 = strArray1;
  if (Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate2 == null)
  {
    // ISSUE: method pointer
    Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate2 = new Func<string, string>((object) null, __methodptr(\u003CMain\u003Eb__0));
  }
  Func<string, string> keySelector1 = Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate2;
  foreach (string str in (IEnumerable<string>) Enumerable.OrderBy<string, string>((IEnumerable<string>) strArray2, keySelector1))
    Console.WriteLine(str);
  string[] strArray3 = strArray1;
  if (Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate3 == null)
  {
    // ISSUE: method pointer
    Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate3 = new Func<string, string>((object) null, __methodptr(\u003CMain\u003Eb__1));
  }
  Func<string, string> keySelector2 = Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate3;
  foreach (string str in (IEnumerable<string>) Enumerable.OrderBy<string, string>((IEnumerable<string>) strArray3, keySelector2))
    Console.WriteLine(str);
}

[CompilerGenerated]
private static string \u003CMain\u003Eb__0(string letter)
{
  return letter;
}

[CompilerGenerated]
private static string \u003CMain\u003Eb__1(string letter)
{
  return letter;
}

编辑: 这里有一个有趣的变化,我很想尝试。在上面的例子中,对于查询表达式,编译器足够聪明,可以优化掉Select。但是,如果在第二个变体中,我们添加了一个显式的Select,那么就有点不足为奇了,但是编译器并不会优化掉显式的.Select(与查询表达式中的显式Select相比 - 这是编译器的要求)。

代码 -

    foreach (string val in letters.OrderBy(letter => letter).Select(letter => letter))
    {
        Console.WriteLine(val);
    }

生成的代码 -

  string[] strArray4 = strArray1;
  if (Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate6 == null)
  {
    // ISSUE: method pointer
    Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate6 = new Func<string, string>((object) null, __methodptr(\u003CMain\u003Eb__2));
  }
  Func<string, string> keySelector3 = Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate6;
  IOrderedEnumerable<string> orderedEnumerable = Enumerable.OrderBy<string, string>((IEnumerable<string>) strArray4, keySelector3);
  if (Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate7 == null)
  {
    // ISSUE: method pointer
    Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate7 = new Func<string, string>((object) null, __methodptr(\u003CMain\u003Eb__3));
  }
  Func<string, string> selector = Program.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate7;
  foreach (string str in Enumerable.Select<string, string>((IEnumerable<string>) orderedEnumerable, selector))
    Console.WriteLine(str);

大家好,感谢所有的回答!问题已经被解答多次了,但我选择接受这个答案是因为它提供了带有生成代码的示例。 - Rob

2

你的第一个查询相当于

... = letters.OrderBy(letter => letter).Select(letter => letter);

你的第二个查询是什么?
... in letter.OrderBy(l => l)) {

这两个查询几乎一模一样,只是第一个查询调用了额外的.Select(...)函数来选择给定的输入,所以它基本上没有意义。C#编译器可能会删除这个函数调用,但你需要查看生成的IL代码才能知道。

此外,你的第一个语句并没有执行查询。.OrderBy(...)和大多数Linq语句都是查询定义。这意味着你对.OrderBy(...)的调用实际上并没有被执行,它是一个“未被回答的问题”,直到你迭代结果时才被回答。因此,在你的两个版本中,当foreach (... in <collection>)访问要迭代的集合时,查询才会被执行。

结论:性能差异将非常小,你甚至需要非常努力才能发现任何真正的差异。当然,这只是我的猜测。


第一个查询有一个额外的.Select(...)调用,其中选择给定的输入,因此它是相当无意义的。如果没有这个,它甚至在查询语法中都无法编译(在C#中与VB.NET不同)。 - Tim Schmelter
@TimSchmelter 正确。我并不建议在查询语法中删除 select ..,我只是指出从功能角度来看它没有任何有用的作用。 - Maarten

0
我唯一能看到的区别是,在第一段代码中,Iteratorforeach之前创建(而不是执行),并将其存储在本地变量中。在第二段代码中,foreach直接调用迭代器上的GetEnumarator方法,而不是使用本地变量。但当然这不会对性能产生任何影响。除此之外,我更喜欢第二个,因为它更易读。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接