C# foreach循环 - 是否保证顺序*稳定*?

16

假设我有一个特定的集合。在不以任何方式改变集合的情况下,我用foreach两次循环遍历其内容。除了宇宙射线等异常情况外,这两个循环中的顺序是否一定保持一致?

或者,给定一个包含若干元素的HashSet<string>,以下代码中注释行的输出结果会不一样的可能原因是什么:

{
    var mySet = new HashSet<string>();
    // Some code which populates the HashSet<string>

    // Output1
    printContents(mySet);

    // Output2
    printContents(mySet);
}

public void printContents(HashSet<string> set) {
    foreach(var element in set) {
         Console.WriteLine(element);
    }
}

如果我能得到一个通俗易懂的答案,解释一下是什么导致实现不符合上述标准,那将会很有帮助。尤其是我对字典(Dictionary)列表(List)和数组特别感兴趣。


1
什么类型的集合? - Kevin DiTraglia
1
我最感兴趣的是字典,但了解所有内容会很好。 - Superbest
1
鉴于.NET的实现方案有限,我认为你最好研究它们的源代码/反编译以获得更好的运气。这不像C++那样存在无数种实现方式,错误的假设会导致未定义的行为…… - user541686
字典不能保证枚举的顺序,这时可以使用排序字典。 - scottheckel
2
好问题。现在请原谅我,我要创建一个名为RandomList的类,其中枚举器会随机迭代元素。嘿嘿哈哈! - Nick Babcock
@scottheckel 但是可以合理地期望枚举在字典被修改之前是稳定的。这使得在只读方式下使用集合时许多事情变得更容易,而不需要排序字典可能具有的(可能微不足道的)开销。 - binki
4个回答

15

数组枚举保证顺序。

ListList<T> 期望提供稳定的顺序(因为它们应该实现顺序索引元素)。

字典和哈希集明确不保证顺序。很少会出现两次迭代项目的调用返回不同顺序的情况,但没有任何保证或期望。一个人不应该期望任何特定的顺序。

排序版本的 Dictionary/HashSet 按排序顺序返回项。

其他 IEnumerable 对象可以自由地做任何它们想做的事情。通常,我们以这样的方式实现迭代器,以匹配用户的期望。即对于某些具有隐式顺序的东西的枚举应该是稳定的,如果提供了显式顺序,则应该是稳定的。未指定顺序的数据库查询应该预计按半随机顺序返回项目。

请查看此问题以获取链接:Does the foreach loop in C# guarantee an order of evaluation?


5
任何实现 IEnumerable<T> 的对象都按自己的方式来实现,没有通用保证任何给定的集合必须确保稳定性。
如果您特别指的是 Collection<T> (http://msdn.microsoft.com/en-us/library/ms132397.aspx),我在它的 MSDN 参考中没有看到任何关于顺序一致性的具体保证。
它可能会是一致的吗?是的。有书面保证吗?我找不到。

太好了!更不用说集合的顺序无关紧要,因此 HashSet 的定义仍然是正确的,不会总是以相同的顺序返回元素。但是 SortedSet 则肯定会。 - scottheckel
@Hexxagonal:使用 HashSet 可以正确地不保证迭代的稳定顺序。实际上,如果在两次迭代之间不更改其内部状态,则可能会获得相同的顺序。第一次迭代不太可能对内部状态进行任何更改。但是,也有可能发生更改。因此没有保证 :-) - Eric J.
Collection<T>.GetEnumerator() 如何才能不以稳定的顺序返回内容?它定义了 Collection<T>.Item(int),这意味着您可以执行 for (var i = 0; i < collection.Count; i++) { var x = collection[i]; } 并保证看到所有元素。如果没有稳定的枚举顺序,这是不可能发生的,对吗?或者,至少,您可以编写自己的枚举器,使用 Item(int),因为它必须是稳定的才能使用。 - binki
@binki:实际实现而言,你是对的。不能保证Collection<T>确实按照这种方式工作,但当前的实现(以及可能的未来实现)都是如此。 - Eric J.

3
对于许多C#集合,都有相应的排序版本。例如,HashSet 相当于 SortedSet,而 Dictionary 则相当于 SortedDictionary。如果你处理的东西不关心顺序(比如 Dictionary),那么你不能假设循环顺序每次都会表现得相同。

0

根据您的HashSet<T>示例,我们现在有源代码可以检查:HashSet:Enumerator

目前为止,Slot[] set.m_slots数组被迭代。该数组对象仅在TrimExcessInitialize(两者仅在构造函数中调用)、OnDeserializationSetCapacity(仅由AddIfNotPresentAddOrGetLocation调用)方法中更改。

m_slots的值仅在更改HashSet元素的方法中更改(ClearRemoveAddIfNotPresentIntersectWithSymmetricExceptWith)。

因此,如果没有任何操作接触到集合,则它将按相同顺序枚举。

Dictionary:Enumerator 的工作方式与之相似,迭代一个 Entry[] entries 数组,只有在调用非只读方法时才会发生更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接