在这种情况下,IEnumerable 被订购了多少次?

How many times is ordered the IEnumerable in this case?

我知道 IEnumerable<T> 在调用之前不会迭代。

假设我有这个代码:

foreach(int iteratorInt in myIEnumerable.OrderBy(x => x))
{
    if(iteratorInt == myIEnumerable.First())
    {
        // do something
    }
}

if 中,我正在检查第一个元素,因此每次迭代都必须对 myIEnumerable 进行排序,以查看哪个是第一个元素,或者它只被排序一次?

对于此代码:

foreach(int iteratorInt in myIEnumerable.OrderBy(x => x.MyProperty))

OrderBy只执行一次

您的 Enumerable 将只订购一次,此处:myIEnumerable.OrderBy(x => x)
在这一行 if(iteratorInt == myIEnumerable.First()) 将不会再次订购。

可能你误解了IEnumerable.First方法,IEnumerable.FirstIEnumerable.OrderBy方法没有任何关系。

Console.WriteLine(new [] {3, 2, 1}.First());
// here you get 3, not 1.    

你可以在这里看到自定义的OrderBy方法:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {   
        var myIEnumerable = GetMyEnumerable();
        foreach(var item in myIEnumerable.MyCustomOrderBy(x => x))
        {           
            if(item == myIEnumerable.First())
            {
                Console.WriteLine("The condition is true with: " + item);
            }
        }
    }   

    public static IEnumerable<int> GetMyEnumerable()
    {           
        foreach(var i in new int[] {5, 4, 3, 2, 1})
        {
            Console.WriteLine("GetMyEnumerable was called " + i);   
            yield return i;
        }       
    }       
}

public static class OrderByExtensionMethod
{
    public static IOrderedEnumerable<TSource> MyCustomOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        Console.WriteLine("OrderByExtensionMethod was called"); 
        return source.OrderBy(keySelector);
    }
}

输出:

OrderByExtensionMethod was called
GetMyEnumerable was called 5
GetMyEnumerable was called 4
GetMyEnumerable was called 3
GetMyEnumerable was called 2
GetMyEnumerable was called 1
GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5
The condition is true with: 5     

发生了什么事?
首先调用了MyCustomOrderBy方法,他需要遍历整个集合,对元素进行排序。

OrderByExtensionMethod was called
GetMyEnumerable was called 5
GetMyEnumerable was called 4
GetMyEnumerable was called 3
GetMyEnumerable was called 2
GetMyEnumerable was called 1 

然后你的foreach开始,myIEnumerable.First()对每一项执行:

GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5
GetMyEnumerable was called 5

终于如愿以偿了:

The condition is true with: 5

OrderBy只计算一次,然而,每次迭代都会在原来无序myIEnumerable的基础上创建一个新的IEnumerator<T>,它可能不会匹配iteratorInt 在第一次迭代时,除非第一个元素恰好是将被排序到第一个位置的元素。

如果您希望 iteratorInt 的第一次迭代值与可枚举的 First() 结果相匹配。您可能希望在循环 之前创建有序可枚举 的临时副本,如下所示:

var list = myIEnumerable.OrderBy(x => x);
foreach(int iteratorInt in list)
{
    if(iteratorInt == list.First())
    {
        // do something
    }
}

尽管这是一个毫无意义的模式(类似于“Loop-switch”反模式),因为它可以简化为:

//do something with list.First();

使用 LINQ 扩展时,查询只会在请求时执行,否则称为延迟执行。当多次请求同一个查询时,每次都会重新评估基础查询,除非初始查询已通过 .ToArrary().ToList().

之类的内容具体化

问题还不是很清楚,所以我将提供一些示例来演示各种行为。

示例 1:

  • 将初始请求设置为局部变量。
  • 在 foreach 中应用 LINQ 查询来订购集合。
  • 使用初始局部变量查找第一个结果。
  • 不要实现任何结果。

代码:

private static void Ex1()
{
    Console.WriteLine("A");

    IEnumerable<int> myIEnumerable = GetEnumerable();

    Console.WriteLine("B");

    foreach (int i in myIEnumerable.OrderBy(x => x))
    {
        Console.WriteLine("*** foreach : " + i);
        if (i == myIEnumerable.First())
        {
            Console.WriteLine("=== Matched .First() : " + i);
        }
    }

    Console.WriteLine("C");
}

示例 2:

  • 将初始请求设置为局部变量。
  • 在 foreach 外部应用 LINQ 查询以在不具体化结果的情况下对集合进行排序。
  • 使用有序查询查找第一个结果
  • 不要实现任何结果。

代码:

private static void Ex2()
{
    Console.WriteLine("A");

    IEnumerable<int> myIEnumerable = GetEnumerable();

    Console.WriteLine("B");

    var ordered = myIEnumerable.OrderBy(x => x);

    foreach (int i in ordered)
    {
        Console.WriteLine("*** foreach : " + i);
        if (i == ordered.First())
        {
            Console.WriteLine("=== Matched .First() : " + i);
        }
    }

    Console.WriteLine("C");
}

示例 3:

  • 将初始请求设置为局部变量。
  • 在 foreach 外部应用 LINQ 查询来排序集合并具体化结果。
  • 使用有序查询查找第一个结果。

代码:

private static void Ex3()
{
    Console.WriteLine("A");

    IEnumerable<int> myIEnumerable = GetEnumerable();

    Console.WriteLine("B");

    var ordered = myIEnumerable.OrderBy(x => x).ToArray();

    foreach (int i in ordered)
    {
        Console.WriteLine("*** foreach : " + i);
        if (i == ordered.First())
        {
            Console.WriteLine("=== Matched .First() : " + i);
        }
    }

    Console.WriteLine("C");
}

所有查询都使用相同的方法来获取可枚举:

private static IEnumerable<int> GetEnumerable()
{
    Console.WriteLine("~~~ GetEnumerable Start");
    foreach (int i in new[]{3, 2, 1})
    {
        Console.WriteLine(">>> yield return : " + i);
        yield return i;
    }

    Console.WriteLine("~~~ GetEnumerable End");
}

最终结果为:

====================
Ex A
====================
A
B
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
*** foreach : 1
~~~ GetEnumerable Start
>>> yield return : 3
*** foreach : 2
~~~ GetEnumerable Start
>>> yield return : 3
*** foreach : 3
~~~ GetEnumerable Start
>>> yield return : 3
=== Matched .First() : 3
C

====================
Ex B
====================

A
B
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
*** foreach : 1
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
=== Matched .First() : 1
*** foreach : 2
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
*** foreach : 3
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
C

====================
Ex C
====================

A
B
~~~ GetEnumerable Start
>>> yield return : 3
>>> yield return : 2
>>> yield return : 1
~~~ GetEnumerable End
*** foreach : 1
=== Matched .First() : 1
*** foreach : 2
*** foreach : 3
C