退出后循环返回(涉及 IEnumerable)导致边界错误

While loop returning AFTER exit (IEnumerable involved) causing bounds error

我知道有很多方法可以给这只猫蒙皮,但是我在正确退出的基本 while 循环中遇到了一些问题,但是 returns(并增加了计数器)在 returning 来自父函数的 IEnumerable.ToList() 。基本上我想做的是搜索单个关键字列表并过滤结果集,直到我只得到包含所有关键字的项目。

它退出循环,然后一旦 return 被击中,它就返回到循环,并且 i 变量递增到另一个位置(高于 count 变量),所以坏了[i]抛出越界错误。

虽然我知道有更好的方法来执行搜索,但我也很想知道为什么代码会返回到已退出的循环、再次递增并导致错误 - 以及如何修复它。这很奇怪,我以前从未见过。是因为 .ToList 强制将枚举备份到链中吗?

代码:

public IEnumerable<Item> GetItemsForSearch(string search=null)
    {
        ParserDataContext data = new ParserDataContext();            
        if (search != null && search != "")
        {                   
            string[] broken = search.Split(' ');                
            IEnumerable<Item> watches = data.Items.Where(x => x.Title.ToLower().Contains(broken[0].ToLower())).OrderByDescending(x => x.DateListed).ThenByDescending(x => x.ID);

            int i = 1;
            int count = broken.Count();
            while (i < count)
            {
                if (broken[i] != null)
                item = items.Where(x => x.Title.ToLower().Contains(broken[i].ToLower()));
                i++;
            }

            return items.Take(50).ToList();
        }

}

While I know there are better ways to execute the search, I'm also very interested in WHY the code is going back into an exited loop, incrementing again, and causing the error

首先要了解实际发生的事情:代码不会返回到已经退出的循环中,它只是在错误的时间点使用变量 i 的值,即在它增加之后。这是一个已知功能:编译器直接捕获 broken[i]i,而不进行复制。

显然,这个功能已经够烦人了,微软宣布修复了 C# 5.0 中这个错误的常见情况(具体来说,关闭 foreach 循环中的循环变量)。

and how to fix it.

对于 C# 4.5 的用户,一个简单的解决方法是声明一个临时变量而不是使用 i。编译器将捕获临时值,使行为符合您的预期:

while (i < count) {
    if (broken[i] != null) {
        var tmp = broken[i].ToLower();
           items = items.Where(x => x.Title.ToLower().Contains(tmp));
        }
        i++;
    }
}

更好的方法是将所有断词收集在一起,然后 运行 一次性选择一个,如下所示:

var allBroken = broken.Where(b => b != null).Select(ToLower).ToList();
var items = items
    .Select(item => new {Item = item, Title = item.Title.ToLower()})
    .Where(pair => allBroken.Any(broken => pair.Title.Contains(broken)))
    .ToList();