【问题标题】:Does the foreach the same as classic for?foreach 和classic for 一样吗?
【发布时间】:2014-09-08 12:18:04
【问题描述】:

关于实现this[int] 的集合和假设集合在枚举期间不会改变foreach (var item in list) 循环是否会在任何时候产生与for (var i = 0; i < list.Count; ++i) 相同的序列?

这意味着,当我需要按索引升序时,我可以使用foreach 还是更安全地使用for?或者它只是取决于当前的集合实现并且可能会随时间变化或变化?

【问题讨论】:

  • 通常foreach 基于GetEnumerator 方法(有时在数组上除外)。
  • 如果您需要知道索引,请使用for 否则,您的选择
  • @PatrickHofman ++i 也很常见,尤其是对于 C++ 程序员。如果i 是复杂类型,它可以产生更好的性能。
  • @Sayse i++ 修改变量但返回原始值。所以它需要创建一个副本。如果无法优化该副本,则会降低性能。对于整数,任何体面的编译器都会对其进行优化,但在 C++ 中,通常会使用更复杂的迭代器,而编译器可能无法做到这一点。在 C# 中,这无关紧要,因为你不能直接重载 i++++i,你只能重载一个共享增量函数。
  • @Sayse 也有人问过here

标签: c# foreach enumeration ilist


【解决方案1】:
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 作为链表,您不太可能在枚举器实现中使用索引器,因为它的效率非常低。 根据经验,使用foreach 可确保您为当前的课程使用最有效的算法,因为它是课程创建者选择的算法。在最坏的情况下,您只会遭受很小的间接开销,这不太可能引起注意。

在 nos 的评论后编辑 2

存在两种构造的语义差异很大的情况:集合修改。

使用简单的for 循环时,如果您在迭代时更改集合,则不会发生任何特别的事情。该程序的行为就像假设您知道自己在做什么一样。这可能会导致某些值多次迭代或跳过其他值,但只要您不访问索引器范围之外的值(这将需要多线程程序不会发生),也不例外。

使用foreachloop 时;如果在迭代集合时修改集合,则会输入未定义的行为。 documentation 告诉我们

只要集合仍然存在,枚举器就保持有效 不变。如果对集合进行了更改,例如添加, 修改或删除元素,枚举数不可恢复 无效且其行为未定义。 在这种情况下,预计大多数 C# 内置类型都会抛出 InvalidOperationException,但一切都可能发生在自定义实现中,从丢失值到重复值,包括无限循环......

【讨论】:

  • 如果有问题的IList<T> 是框架提供的List<T>,那么索引器是在内部使用的,但这不是IList<T> 特有的,它恰好是它的方式已实施。
  • @AdamHouldsworth:非常真实。但正如我在回答中指出的那样,通常情况并非如此。例如,您可以轻松创建实现IList<T> 的链表。在这种情况下,枚举器不太可能在内部使用索引器(因为链表中的索引器效率不高)。
  • OP 没有提到任何IList,只是一个名为list 的变量,它是一个实现this[int] 的集合。
  • @Gutblender :他将 IList 放在标签中,所以我认为这就是他的想法。
  • 特别感兴趣的是您修改正在迭代的集合的情况,使用foreachfor 的行为会有很大不同
【解决方案2】:

一般来说,是的,但严格来说:不是。这真的取决于实现。

通常使用for,您将使用this 索引器属性。 foreach 使用 GetEnumerator() 来获取遍历集合的枚举器。根据具体实现,枚举器可能会产生不同于 for 的结果。

列表的隐含逻辑是它具有特定的顺序,并且在实现IList 时,您可以声明是否可以假设两个索引器属性作为枚举器的顺序相同。

【讨论】:

  • 我认为返回不同序列的索引器和枚举违反了 IList<T> 的隐含合同(即使微软可能没有费心明确指定该合同)
  • 我没有提到IList<T>,也没有提到OP。对于IList<T> 和数组,我同意顺序相同。
  • 问题标记为 [ilist]
  • 请更具体一点(是哪个问题?)因为答案真的是no,那么yes:不,不涉及索引器, 而IEnumerator 无处使用索引,但是您可以通常指望按顺序迭代的事物。
【解决方案3】:

无法保证会是这种情况。代码路径可以完全分开。当然,像 List 这样的集合会产生相同的结果,但您可以编写不会产生相同结果的数据结构(甚至是有用的数据结构)。

索引器只是一个带有附加索引参数的属性。如果您愿意,可以返回一个随机值。

【讨论】:

    【解决方案4】:

    您应该牢记一个重要的想法,因为两者之间的区别在于,在 foreach 内部您不能/无法对枚举对象进行任何更改。 如果你想改变(基本上是删除)枚举中的对象,你必须使用 for 循环

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-12
      • 2020-05-07
      • 1970-01-01
      • 2015-10-18
      • 2014-04-06
      相关资源
      最近更新 更多