【问题标题】:How many times is ordered the IEnumerable in this case?在这种情况下,IEnumerable 被订购了多少次?
【发布时间】:2017-10-20 10:09:23
【问题描述】:

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

假设我有这个代码:

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

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

【问题讨论】:

  • 只有一次,for 循环就是这样工作的。
  • 不能编译因为myIEnumerable.OrderBy(x => x.MyProperty) 返回IEnumerable<SomeClass> 而不是IEnumerable<int>
  • @TimSchmelter 谢谢,我已更改为 OrderBy(x => x)。那是一个错误。无论如何,这只是一个示例,表明我正在订购并稍后检查第一个元素。
  • @TimSchmelter:如果我没记错的话,除非为SomeClassint 创建了隐式转换?
  • 您可以在循环外确定myIEnumerable.First(),然后就没有问题了。多次执行。

标签: c# linq ienumerable


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    您的 Enumerable 只会被订购一次,这里:myIEnumerable.OrderBy(x =&gt; 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
    

    【讨论】:

    • 您可能需要考虑将您的 for 循环更改为 foreach(var item in GetMyEnumerable().OrderBy(x=&gt;x)),以便更好地匹配问题,并表明 OrderBy 不会出现多次。
    【解决方案3】:

    对于此代码:

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

    OrderBy 只执行一次

    【讨论】:

      【解决方案4】:

      OrderBy 只计算一次,但是,每次迭代都会创建一个基于原始无序 myIEnumerable 的新 IEnumerator&lt;T&gt;,并且它可能不会在第一次迭代中匹配 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();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-23
        • 2017-02-27
        • 1970-01-01
        • 1970-01-01
        • 2020-06-05
        相关资源
        最近更新 更多