关于实现this[int]
和的集合,假设集合在枚举期间不会发生变化,foreach (var item in list)
循环是否产生与{{1}相同的序列任何时候?
这意味着,当我需要按索引升序时,我可以使用for (var i = 0; i < list.Count; ++i)
还是只使用foreach
更安全?或者它只取决于当前的收集实施并且时间变化或变化?
答案 0 :(得分:2)
foreach (var item in list)
{
// do things
}
转换为
var enumerator = list.GetEnumerator();
while(enumerator.MoveNext())
{
var item = enumerator.Current;
// do things
}
正如您所看到的,在一般情况下,它没有使用索引器list[i]
。
但是,对于大多数集合类型,语义是相同的。
修改强>
有IList<T>
个实现,其中枚举器IList&lt; T&gt;作为一个链表,你很可能不会在你的枚举器实现中使用索引器,因为它效率很低。
根据经验,使用foreach
确保您为手头的类使用最有效的算法,因为它是由班级选择的算法。造物主。在最糟糕的情况下,您只会遇到一个很小的间接开销,这种开销很可能不会引起注意。
在评论后编辑2
有一种情况是两种结构的语义变化很大:集合修改。
使用简单的for
循环时,如果在迭代时更改集合,则不会发生任何特殊情况。该程序的行为就像它假设您知道自己在做什么一样。这可能会导致某些值迭代多次或其他跳过,但只要您不在索引器范围之外访问(这将需要多线程程序),就不会有异常。
使用foreach
循环时;如果在迭代时修改集合,则输入未定义的行为。 documentation告诉我们
只要收集仍然存在,枚举器仍然有效 不变。如果对集合进行了更改,例如添加, 修改或删除元素,枚举器是不可恢复的 无效,其行为未定义。 在这种情况下,期望大多数C#内置类型抛出
InvalidOperationException
,但一切都可以在自定义实现中发生,从错过的值到重复的值,包括无限循环......
答案 1 :(得分:1)
一般来说,是的,但严格地说:不。这实际上取决于实施。
通常使用for
,您将使用this
索引器属性。 foreach
使用GetEnumerator()
来获取迭代集合的枚举数。根据实现,枚举器可能会产生除for
之外的其他结果。
列表的隐含逻辑是具有特定顺序,并且在实现IList
时,您可以声明它是保存以假设作为枚举器的索引器属性的顺序是相同的。
答案 2 :(得分:1)
无法保证会出现这种情况。代码路径可以完全分开。当然,像List这样的集合会产生相同的结果,但是你可以编写那些没有的数据结构(甚至是有用的)。
索引器只是一个带有附加索引参数的属性。如果您愿意,可以返回随机值。
答案 3 :(得分:0)
一个重要的想法你应该记住,因为2之间的区别在于你可以对内部红外物体进行任何改变。 如果您希望从枚举中更改(基本删除)对象,则必须使用for循环