【问题标题】:Unexpected behavior for a System.Collections.IEnumerator objectSystem.Collections.IEnumerator 对象的意外行为
【发布时间】:2012-09-07 12:42:09
【问题描述】:

我想删除集合中的所有元素,因为我没有看到 .RemoveAll 或类似的东西,所以我使用下面的代码。

int number = addressBook.Items.Count;
System.Collections.IEnumerator enumerator = ...
while (enumerator.MoveNext())
{
  target = enumerator.Current as Outlook.ContactItem;
  target.Delete();
}

但是,我注意到集合中剩余元素的数量大约是程序每次运行的一半。我的结论是.Delete() 自己跳到下一个元素,这意味着循环条件中的.MoveNext() 跳到下一个元素之后

所以我尝试如下重置枚举器。

int number = addressBook.Items.Count;
System.Collections.IEnumerator enumerator = ...
while (enumerator.MoveNext())
{
  target = enumerator.Current as Outlook.ContactItem;
  target.Delete();
  enumerator.Reset();
}

但是,当我检查.Count 时,我看到在删除最后一个元素并重置枚举器之后,剩余的元素数仍然为 1。当然,我遇到了一个例外。

我在这张照片中遗漏了什么?我知道这不是错误,因为 会在很久以前报告并解决...

【问题讨论】:

  • 你为什么不简单地使用foreach循环???
  • @Jon 这不是你想的那样的集合。这是一个 Outlook 故事。我正在尝试删除联系人,但它与我作斗争。
  • @Tigran 代码中的第 1 行。 addressBook.Items.Count.
  • @Jon - 你不能修改你用foreach循环循环的集合。
  • 附带说明,不确定它是否重要,但是... msdn.microsoft.com/en-us/library/… "要删除文件夹的 Items 集合中的所有项目,您必须删除每个项目从最后一个开始文件夹中的项目。”

标签: c# ienumerable


【解决方案1】:

IEnumerator 只能在基础集合保持不变的情况下使用。一旦它发生变化(例如通过从中删除元素),枚举器就会不可恢复地失效。

您不能在修改集合时使用一个枚举器来浏览集合,而 Reset 也无济于事。您只需要每次都创建一个新的枚举器。例如。类似的东西可能会起作用:

while (addressBook.Items.Count > 0)
{
    foreach(Outlook.ContactItem target in ...)
    {
        target.Delete();
        break; // <-- !
    }
}

【讨论】:

  • 或使用 linq:while (addressBook.Items.Count &gt; 0) addressBook.Items.OfType&lt;Outlook.ContactItem&gt;().First().Delete();
  • @KooKiz 您的 LINQ 魔法似乎奏效了,但我强烈不喜欢我需要自己遍历集合...将您的代码作为答案,以便人们可以给它评分!并将其与您找到的链接结合起来。我一直在寻找它,但一定错过了。 Grrr 目前适用于 InterOp 和 Outlook。 :)
【解决方案2】:

两件事。

  1. 只要在删除每个项目后重新实例化它,就应该能够使用枚举器删除所有项目。使用 LINQ,它应该如下所示:

    while (addressBook.Items.Count > 0)
    {
        addressBook.Items
            .OfType<Outlook.ContactItem>()
            .Last()
            .Delete();
    }
    
  2. 根据 MSDN,您实际上必须删除 从最后一项开始的项目:http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._contactitem.delete.aspx

【讨论】:

  • 很好的链接,但我质疑该方法的连贯性。为什么我需要先删除最后一个对象?我想,假设我只需要删除一个联系人,然后,第二天,再删除一个,等等。我最终将按照描述的方法删除 all 联系人 sans 仍在执行有效操作。那么,为什么要费心遵循那种的方式呢?我在逻辑上是正确的还是我错过了什么?在我看来,.First() 应该比.Last() 快​​一点点。
  • @KonradViltersten 我同意。更令我惊讶的是,如果您查看 Office 2007 的文档(我提供的链接是针对 Office 2010),MSDN 上没有任何建议。所以它是最近的东西,而不仅仅是一些遗留的东西。
【解决方案3】:

Marc Gravell's Answer

重置是多余的;如此之多,以至于它是在 迭代器块的语言规范以在重置时引发异常。这 正确的做法是简单地处理和释放旧的迭代器, 并再次调用 GetEnumerator。或者更好:避免阅读 两次,因为并非所有数据都是可重复的。

参考以下链接:

Can't add/remove items from a collection while foreach is iterating over it
When IEnumerator.Reset() method is called?

using IEnumerator to removing items

希望对您有所帮助..

【讨论】:

    【解决方案4】:

    我希望使用Enumerable.Reverse 扩展会是对while...Last().Delete() 解决方案的可喜改进。这可能是这样的:

    foreach(var target in addressBook.Items
        .OfType<Outlook.ContactItem>()
        .Reverse())
    {
        target.Delete();
    }
    

    我不经常使用Reverse;事实上,我不记得曾经在生产代码中使用过它,但我也不必与 Outlook 进行互操作。这里的好处是您不会像while...Last().Delete() 方法那样在每次删除项目后重复遍历整个列表剩余部分。当项目列表很小时,性能可能没有太大(任何?)差异,但是如果遍历很昂贵(我想这是因为这可能归结为引擎盖下的 COM iterop 调用)或者如果列表是大,那么性能差异可能很大。唯一确定的方法是测试/配置文件。

    【讨论】:

    • 你能foreach 一个你同时改变的集合吗?另外,我认为 MS 在这一点上是错误的,删除最后一个元素是一种误解。否则,删除第一个元素(仅)是无效的,然后在第二天删除新的第一个元素等,直到一切都消失。永远不要少 - Reverse 的一个很酷的用法。实际上,我也从未见过它在行动中。令我困惑的是,尽管有这么多回复,但没有人认为 问题 很有趣......
    • Reverse 的迭代器在迭代之前读取(它必须)中的所有内容(除非他们已经完成了检测ICollection 枚举的优化) - 所以通常我会说你是对的但我不确定这里;无论如何,修复它所需要做的就是在迭代之前调用 ToList - 然后你将迭代列表实例,而不是原始的 IEnumerable 实例。
    • @KonradViltersten,我没有提到您的示例中的 GetEnumerator...while(enumerator.MoveNext()) 在逻辑上等同于使用 foreach 循环(除了循环退出时 foreach 将在枚举器上调用 Dispose 。)
    • 嗯...如果迭代器需要读入所有内容,我相当肯定.First() 会更快(或者至少在任何情况下都不会变慢)。我很高兴这个话题引起了如此大的讨论。两天前我发布了另一个问题,那个变成了真正的风滚草...... :)
    • 我想我明白你在说什么 - 你确定的实现在 while not empty 循环中执行 First()。我的 cmets 旨在删除从后到前的项目 - 如果不需要,那么 First() 似乎是要走的路。无论如何-我认为您的问题很有趣。 :干杯:
    猜你喜欢
    • 2017-05-31
    • 2023-03-22
    • 2023-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多