【问题标题】:Using .First() and .Count() broke a IEnumerable?使用 .First() 和 .Count() 破坏了 IEnumerable?
【发布时间】:2012-03-12 09:31:52
【问题描述】:

这是我的代码:

protected IEnumerable<Hotel> Hotels;
protected Hotel Hotel;

Hotels = from Hotel hotel in new Hotels()
         select hotel;

Response.Write("RES " + Hotels.Count() + "<br />");
if (Hotels.Count() > 0)
{
    Hotel = Hotels.First();
}
Response.Write("RES " + Hotels.Count() + "<br />");
Response.Write("RES " + Hotels.Count() + "<br />");

嗯,酒店有 1 项。但是结果呢:

RES 1
RES 0
RES 1

为什么?似乎.First() 与迭代器混淆了?如何使用 IEnumberable 修复它? (不使用其他类型的列表,我需要 IEnumerable)。

【问题讨论】:

  • Hotels 是如何填充的以及底层存储是什么,例如它实际上是在引用 List 吗?
  • 行为取决于酒店的具体情况。如果它包含对可以更改的数据源的某些引用/连接,那么显然该值可以在对 Count 的调用之间更改,或者如果它是通过 yield return 实现的,它可能会做任何事情。尝试在它上面调用 ToArray(),然后对 ToArray() 返回的值进行计数等,然后你就知道你有一个固定的集合快照。
  • Hotels 的实际实现可能有问题。例如,它适用于列表。
  • 哪个类实现了 IEnumerable?你如何初始化Hotels 变量?它适用于列表数组等。顺便说一句,您可以使用 FirstOrDefault() 扩展方法而不是 if 构造。
  • 刚刚在带有List的控制台应用程序中对其进行了测试,它按预期返回了1、1、1。

标签: c# .net list ienumerable


【解决方案1】:
static void Main(string[] args)
{
    IEnumerable<String> Hotels = new List<String>{"sdsfsdf"};
    String Hotel;

    Console.WriteLine("RES " + Hotels.Count());
    if (Hotels.Count() > 0)
    {
        Hotel = Hotels.First();
    }
    Console.WriteLine("RES " + Hotels.Count());
    Console.WriteLine("RES " + Hotels.Count());
}

打印

RES 1
RES 1
RES 1

正如预期的那样,您如何填充可枚举以及您将其创建为什么类型?

如果您通过未执行的可查询来填充此内容,您可能会出现类似这样的奇怪行为,因为它会选择第一个用法(即在这种情况下为 First())。

【讨论】:

  • 你能发布你的linq语句吗?这也是直接来自数据库吗?
  • 将 .ToArray() 添加到 linq 的末尾。这将强制查询在 .First() 之前填充到内存中,这意味着它将针对 .net 对象执行
  • 嗯...添加 .ToArray() 有效!但是,为什么会有这种行为?能给我解释一下吗?
  • @markzzz: ToArray() 执行底层 LINQ 查询并返回一个集合本身,因此 Count() 将始终引用相同的项目列表
  • 是的,发生的事情是您正在执行每个顺序语句而不是 IQueriable。发生的情况是查询提供程序决定如何将其转换为 SQL,我想当您第一次调用时,它会将您的实体作为查询而不是可枚举的,因此清除基本集合
【解决方案2】:

这里没有魔法。看起来Hotels 集合中的项目数量随着时间的推移而变化,可能是一些延迟执行的 LINQ 查询,甚至可能是 LINQ-to-SQL。

请显示填充酒店的完整代码。

【讨论】:

    【解决方案3】:

    根据底层实现,不保证以任何方式重新枚举 IEnumerable 会返回相同的值。要了解原因,请考虑这个示例;

    static void Main(string[] args)
    {
        IEnumerable<int> bop = RandomSequence();
        Console.WriteLine(bop.Count());
        Console.WriteLine(bop.Count());
    }
    
    private static int _seed = 0;
    static IEnumerable<int> RandomSequence()
    {
        var random = new Random(_seed++);
        int randomNumber;
        while ((randomNumber = random.Next(100)) != 0)
            yield return randomNumber;
    } 
    

    这是一个完全有效的 IEnumerable,对 Count() 的两次调用将评估为两个不同的伪随机值。原因是 Count() 重新枚举了相同的 IEnumerable 并生成了完全不同的随机序列。

    如果您想要可重复的枚举,则需要在可枚举上调用ToList()ToArray 来存储结果,并从每次以相同方式枚举的列表/数组中进行所有枚举。

    【讨论】:

      【解决方案4】:

      “First”方法可能使迭代器前进以指向第二个元素。您对“Count”的第一次调用会完成对一个元素序列的迭代,随后对 count 的调用会重新开始迭代。 Count 总是会迭代整个序列,所以它是幂等的。您需要在First() 之后重置枚举器Hotels.Reset() 以使Count() 行为正确。

      最简单的方法可能是自己创建一个Reset 扩展,但我无法重现您的问题。如果您有一些系统生成的数据库迭代器,它可能不会自动重置,您可以使用下面的代码。它只是利用您自己的发现,即单个 Count() 会重置迭代器。这将是O(n) 来完成。

      static class Extensions
      {
          public static void Reset<T>(this IEnumerable<T> toReset )
          {
              if (toReset != null)
              {
                  int i = toReset.Count();
              }
          }
      }
      

      但我无法重现您的问题:

      下面的代码始终给出正确的结果。您是否编写了自己的枚举器或集合?

      static void Main(string[] args)
      {
          var Response = System.Console.Out;
      
          var Hotels = new[]{1, 2, 3, 4};
          var Hotel = 0;
      
          Response.Write("RES " + Hotels.Count() + "<br />");
          if (Hotels.Count() > 0)
          {
              Hotel = Hotels.First();
          }
          Response.Write("RES " + Hotels.Count() + "<br />");
          Response.Write("RES " + Hotels.Count() + "<br />");
      
          Console.WriteLine( "Hotel: " + Hotel);
      }
      

      【讨论】:

      • IEnumerable 上没有 .Reset() 方法,已检查:(
      • 不,它在枚举器上。 Hotels 使用的是哪个集合? -- 我无法使用数组重现您的错误。
      • markzzz,我想你使用了一些奇怪的对象作为你的 IEnumerable。可能是你自己的实现把事情搞砸了。
      • 那么我认为底层数据源会随着时间的推移而变化,因此会产生不同的结果......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-09
      • 1970-01-01
      • 1970-01-01
      • 2021-01-22
      相关资源
      最近更新 更多