collection/its 元素上的哪些操作使枚举器无效

Which operations on a collection/its elements are invalidating the enumerator

IEnumerable<T>.GetEnumerator method 的文档中所述 对集合的某些操作可能会使枚举器无效。很明显,添加或删除元素会产生上述效果。但究竟什么才算修改集合呢?枚举器是否关心集合本身元素的变化?

这个问题的答案可能隐藏在this thread的答案中,但我遗漏了一些例子。

示例代码:

public class CollectionElement
{
  public CollectionElement(int id, object someProperty)
  {
    ID = id;
    SomeProperty = someProperty;
  }

  public int ID { get; }
  public object SomeProperty { get; set; }
}

public class CollectionModifier
{
  // This is the example collection. (List<T> implements ICollection<T>.)
  private List<CollectionElement> collection = new List<CollectionElement>();

  // This is another example. (Dictionary<TKey, TValue> implements ICollection<KeyValuePair<TKey, TValue>>)
  private Dictionary<int, CollectionElement> dictionary = new Dictionary<int, CollectionElement>();

  private void Add(int id, object someProperty)
  {
    CollectionElement newElement = new CollectionElement(id, someProperty);

    // Both statements are obviously invalidating the enumerator of the corresponding collection.
    collection.Add(newElement);
    dictionary.Add(id, newElement);
  }

  private void Remove(int id)
  {
    // Both statements are obviously invalidating the enumerator of the corresponding collection.
    collection.RemoveAll(item => item.ID == id);
    dictionary.Remove(id);
  }

  private void ExchangeListElement(int index, CollectionElement newElement)
  {
    if (index >= collection.Count)
      return;
    // According to the comment by Dennis_E the following statement is invalidating the enumerator of the collection.
    collection[index] = newElement;
  }

  private void ModifyElement(int id, object newValue)
  {
    CollectionElement element = collection.FirstOrDefault(item => item.ID == id);
    if (element == null)
      return;
#warning Is the following statement modifying the collection, hence invalidating the enumerator?
    element.SomeProperty = newValue;
  }

  private void ExchangeElement(int id, CollectionElement newElement)
  {
    if (!dictionary.TryGetValue(id, out CollectionElement oldElement))
      return;
#warning Is the following statement modifying the collection, hence invalidating the enumerator?
    dictionary[id] = newElement;
  }
}

这完全取决于实施。添加或删除元素需要使枚举器无效甚至不是真的。由迭代器的实现(以及潜在的底层集合的细节)来确定底层集合的哪些类型的更改使得它不再可能继续枚举它,以及哪些更改仍然可以允许迭代器继续。

一些实现选择了最简单的选项,并说 对集合的任何 更改都会使枚举器无效(许多 .NET 集合都是以这种方式实现的),其他实现则能够继续迭代序列,而不管基础集合有任何变化。有些介于两者之间。

如果您想知道您拥有的给定序列在您更改它所基于的集合时会如何表现,您将必须查看该集合的文档,或者为它生成序列的任何内容。如果您正在为一个集合创建自己的集合 and/or 迭代器,那么由您来决定哪些类型的更改使得任何现有迭代器继续处理不再明智,或者是否值得您花时间(和相关的性能成本)以支持对基础集合的 some/all 更改,同时仍然对正在迭代的序列具有明智的行为。

除了Servy回答:

private int? _someValue;
public IEnumerable<int> GetValues()
{
   if(_someValue != null)
   {
       yield return _someValue.Value;
   }
}

唯一负责此代码正确性的人是代码所有者。如果在 _someValue 更改为 null 时允许枚举 - 您将在多线程环境中获得 NullReferenceException。如果您的对象保持状态,情况会更糟:

private bool _isBlack = false;
private bool _isWhite = true;
public IEnumerable<string> GetValues()
{
   if(_isBlack)
   {
       yield return "black";
   }
   if(_isWhite)
   {
       yield return "white";
   }
}

在这个例子中,如果有人开始枚举,跳到单词 "white" 然后有人启用黑色 - 枚举的人不会得到这个 "black" 值。