代码之家  ›  专栏  ›  技术社区  ›  adeel825

为什么在集合中枚举会引发异常,而在其项中循环则不会

  •  4
  • adeel825  · 技术社区  · 16 年前

    我正在测试一些同步结构,我注意到一些让我困惑的东西。当我在写入集合的同时枚举集合时,它抛出了一个异常(这是预期的),但当我使用for循环遍历集合时,它没有抛出异常。有人能解释一下吗?我认为列表不允许读者和作者同时操作。我本以为在集合中循环会显示出与使用枚举器相同的行为。

    代码如下:

       class Program
       {
        private static List<string> _collection = new List<string>();
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(AddItems), null);
            System.Threading.Thread.Sleep(5000);
            ThreadPool.QueueUserWorkItem(new WaitCallback(DisplayItems), null);
            Console.ReadLine();
        }
    
        public static void AddItems(object state_)
        {
            for (int i = 1; i <= 50; i++)
            {
                _collection.Add(i.ToString());
                Console.WriteLine("Adding " + i);
                System.Threading.Thread.Sleep(150);
            }
        }
    
        public static void DisplayItems(object state_)
        {
            // This will not throw an exception
            //for (int i = 0; i < _collection.Count; i++)
            //{
            //    Console.WriteLine("Reading " + _collection[i]);
            //    System.Threading.Thread.Sleep(150);
            //}
    
            // This will throw an exception
            List<string>.Enumerator enumerator = _collection.GetEnumerator();
            while (enumerator.MoveNext())
            {
                string value = enumerator.Current;
                System.Threading.Thread.Sleep(150);
                Console.WriteLine("Reading " + value);
            }
        }
    }
    
    7 回复  |  直到 16 年前
        1
  •  15
  •   Matt Brunell    16 年前

    枚举集合时不能修改集合。即使没有考虑线程问题,该规则仍然存在。从 MSDN

    只要集合保持不变,枚举数就保持有效。如果对集合进行了更改(如添加、修改或删除元素),则枚举数将不可恢复地失效,并且其行为未定义。

    基于整数的for循环实际上不是枚举数。在大多数情况下,是完成同样的事情。但是,IEnumerator的接口保证可以遍历整个集合。如果在修改集合后调用MoveNext,则平台通过抛出异常在内部强制执行此操作。此异常由枚举器对象引发。

        2
  •  2
  •   Darren Clark    16 年前

    回答你的问题。。。

    枚举时,您将得到一个IEnumerator,该IEnumerator绑定到列表的状态,与您请求它时的状态相同。进一步的操作操作枚举器(MoveNext,Current)。

    当使用for循环时,您正在进行序列if调用,以便按索引获取特定项。没有像枚举器这样的外部上下文知道您处于循环中。所有的收藏家都知道,你只需要一件东西。由于集合从未分发枚举器,因此它无法知道您请求项0、项1、项2等的原因是因为您正在遍历列表。

    如果你在处理列表的同时又在处理它,那么不管怎样你都会出错。如果添加项,则for循环可能会自动跳过一些项,而foreach循环将抛出一些项。如果删除项,那么如果不走运,for循环可能会将索引抛出范围之外,但可能在大多数情况下都有效。

    但我想你已经明白了,你的问题很简单,为什么两种迭代方式的表现不同。答案是,在一种情况下调用GetEnumerator时,以及在另一种情况下调用get\u Item时,集合的状态(对于集合)是已知的。

        3
  •  1
  •   user44484 user44484    16 年前

    枚举列表时,您是在枚举项,而不是索引。因此,当您在枚举时添加项时,会使枚举无效。它是这样构建的,以防止出现这样的情况:您可能会在索引6处插入项目的同时枚举列表中的项目6,您可能会枚举旧的或新的项目,或某些未定义的状态。

        4
  •  1
  •   Chris Ballance    16 年前

    枚举器变为 . 如果您在枚举列表时更改了列表,则需要重新考虑一下您的策略。

        5
  •  1
  •   Guffa    16 年前

    列表有一个内部版本计数器,当您更改列表的内容时,该计数器会更新。枚举器跟踪版本,并在看到列表已更改时引发异常。

    当您只是循环列表时,没有任何内容可以跟踪版本,因此没有任何内容可以捕获列表已更改的内容。

    如果在循环时更改列表,可能会产生不想要的效果,这是枚举器保护您不受影响的。例如,如果在不更改循环索引的情况下从列表中删除某个项,使其仍然指向同一个项,则可能会丢失循环中的项。同样,如果插入项而不更正索引,则可以对同一项进行多次迭代。

        6
  •  0
  •   roman m    16 年前

    在枚举集合时不能更改集合。

        7
  •  -1
  •   Mitch Wheat    16 年前

    这个代码有缺陷,因为你睡了5秒钟,但并不是所有的项目都被添加到列表中。这意味着您在第一个线程将项添加到列表之前就开始显示一个线程上的项,从而导致基础集合发生变化并使枚举数无效。

    卸下线程。睡眠从Add code(添加代码)中突出显示:

    public static void AddItems(object state_)
    {     
       for (int i = 1; i <= 50; i++)      
       {        
           _collection.Add(i.ToString());      
           Console.WriteLine("Adding " + i);  
       }   
    } 
    

    您应该使用一种同步机制,等待第一个线程完成添加项目的工作,而不是休眠。

    推荐文章