linq 在这种情况下,IEnumerable被排序了多少次?

ogsagwnx  于 2023-03-27  发布在  其他
关注(0)|答案(4)|浏览(107)

我知道IEnumerable<T>在被调用之前不会迭代。
假设我有这样的代码:

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

if中,我检查的是第一个元素,那么myIEnumerable是否必须在每次迭代时排序,以确定哪个是第一个元素,或者它只排序一次?

jhdbpxl9

jhdbpxl91#

当使用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
kyxcudwk

kyxcudwk2#

您的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
zed5wv10

zed5wv103#

对于此代码:

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

OrderBy只执行一次

cigdeys3

cigdeys34#

OrderBy只计算一次,但是,每次迭代都会创建一个基于原始无序myIEnumerable的新IEnumerator<T>,并且它可能不会在第一次迭代中匹配iteratorInt,除非第一个元素恰好是排序到第一个位置的元素。
如果你想让iteratorInt的第一次迭代的值匹配enumerable的First()结果,你需要在循环之前创建一个有序enumerable的临时副本,像这样:

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

虽然这是一个相当无意义的模式(类似于“Loop-switch”反模式),但它可以简化为:

//do something with list.First();

相关问题