IEnumerable与LINQ异常行为:遍历Count递减却无修改异常
LINQ确实采用延迟加载(延迟执行)机制,你遇到的所有现象都是这个机制直接导致的,具体拆解如下:
1. 延迟执行的本质
你代码里的copy并不是一个实际存放元素的集合,而是一个查询表达式。OrderBy和Where属于LINQ的延迟操作符,它们不会立即执行并生成结果集合,只会记录查询逻辑。只有当你枚举它(比如foreach遍历)或者调用Count()、Max()这类立即执行方法时,才会重新遍历源集合(也就是all数组),执行排序和过滤逻辑,生成临时的结果序列。
2. 遍历中Count()递减的原因
每次foreach (var rolling in copy)都会重新执行整个查询:先对all数组排序,再过滤出!IsDataLoaded的元素。而你在遍历过程中把rolling.IsDataLoaded = true,所以每次调用copy.Count()时,都会重新执行查询——此时已经有部分元素的IsDataLoaded被设为true,会被过滤掉,因此计数随迭代递减。
3. 未触发"Collection was modified"异常的原因
这个异常的触发条件是枚举集合过程中,集合本身的元素数量被增删修改。但你的源集合是all数组,数组的元素数量从未变化,只是元素的内部属性IsDataLoaded被修改了。LINQ查询的是元素的属性值,而非集合结构,所以不会触发该异常。
4. 调用Max()时集合为空的原因
当你执行copy.Max()时,会再次执行整个查询:此时之前遍历过的元素IsDataLoaded都已被设为true,如果所有元素的这个属性都被修改,Where过滤后就没有元素了,调用Max()会抛出空序列异常,此时copy.Count()自然返回0。
可选解决办法
如果要避免这种动态变化的行为,需要把延迟执行的查询转换成实际的固定集合,比如调用ToList()或ToArray():
var copy = all .OrderBy(x => x.Id) .Where(x => !x.IsDataLoaded) .ToList(); // 立即执行查询,生成内存中的固定列表
这样copy就是一个固定的集合,后续修改元素属性不会影响集合的内容,Count()和遍历结果都会保持一致。
内容的提问来源于stack exchange,提问作者Vadim Belov

