首页 > 解决方案 > Why does Select turn my while loop into an infinite loop?

问题描述

At work I ran into a strange problem, where a loop I expected to terminate was actually running indefinitely.

I traced the problem back to a use of Select. Interestingly, the loop terminated as expected when I added a .ToList() right after the Select. I boiled it down to a small example.

class WrappedBool
{
    public WrappedBool(bool inner)
    {
        InnerBool = inner;
    }
    public bool InnerBool { get; set; } = false;
}

// remove .ToList() here and the following loop will go infinite
IEnumerable<WrappedBool> enumerable = 
   new List<bool>() { false, true, false }
  .Select(b => new WrappedBool(b))
  .ToList();

while (enumerable.Any(wb => !wb.InnerBool))
{
    WrappedBool firstFalse = enumerable.Where(wb => !wb.InnerBool).First();
    firstFalse.InnerBool = true;
}

While I don't have to deal with my code not terminating anymore, I still wonder where this behaviour is coming from in the first place.

标签: c#linq

解决方案


好吧,没有具体化( .ToList())enumerable只是一个查询

IEnumerable<WrappedBool> enumerable = 
   new List<bool>() { false, true, false }
  .Select(b => new WrappedBool(b));

每当您调用它时,它都会创建一个新实例List<bool>() {false, true, false}说明您有false要迭代的项目

// new List<bool>() { false, true, false } - do we have any false item here?
// Yes - keep on looping (forever)
while (enumerable.Any(wb => !wb.InnerBool)) 
{
    // get 1st false from new List<bool>() { false, true, false }
    WrappedBool firstFalse = enumerable.Where(wb => !wb.InnerBool).First();
    // turn it into true and discard
    firstFalse.InnerBool = true;
}

相反

IEnumerable<WrappedBool> enumerable = 
   new List<bool>() { false, true, false }
  .Select(b => new WrappedBool(b))
  .ToList(); // create a List; call new List<bool>() { false, true, false } just once

物化的,所以enumerableList<T>是创建一次并在其中修改 1st 和 3d 项目:

// does enumerable collection (List) have any false item? 
while (enumerable.Any(wb => !wb.InnerBool)) 
{
    // get 1st false from enumerable List
    WrappedBool firstFalse = enumerable.Where(wb => !wb.InnerBool).First();
    // turn it into true 
    firstFalse.InnerBool = true;
}

推荐阅读