Saturday, January 18, 2020

Hello, dear readers!

Today I would like to share with you some tips regarding debugging the so called "Collection Was Modified Exception".
Often, this problem arises when a collection is changed during its enumeration with a foreach statement. The debugging is quite straightforward:
1) get to the problematic foreach statement;
2) set breakpoints in all the places in a code where the enumerated collection is modified;
3) run through the foreach body.
4) find the place of an unexpected collection modification.

But what to do if the collection modification is not so obvious or if the collection is modified in a lot of places? In these situations I prefer temporarily switching the enumerated collection with a special wrapper that is created with a sole purpose of identifying such a situation.

Lets consider a common System.Collections.Generic.List<T>. To create a wrapper that would automatically identify the "Collection Was Modified" situation follow these steps:

1) Create a stub for a new class ChangedDuringEnumerationMonitoringList<T> : IList<T> with an inner List<T> instance:
1:  class CollectionChangedDuringEnumerationMonitoringList<T> : IList<T>  
2:  {  
3:      readonly List<T> _list = new List<T>();  
4:  }  

2) Automatically implement all the IList<T> members by delegating to the inner _list. For example:
1:  public void Add(T item) 
2:  { 
3:      _list.Add(item); 
4:  } 

3) Implement custom IEnumerator<T> that delegates all its functionality to an original enumerator of a list and tells whether it is in a state of enumeration:
1:  public class NotifyingEnumeratorWrapper : IEnumerator<T>, IEnumerator  
2:  {  
3:      // An original enumerator that is delegated to.  
4:      readonly IEnumerator<T> _typedEnumerator;  
5:    
6:      // Injecting an original enumerator instance in a constructor.  
7:      public NotifyingEnumeratorWrapper(IEnumerator<T> enumerator)  
8:      {  
9:          _typedEnumerator = enumerator;  
10:      }  
11:    
12:      // A property that marks that an enumeration is in a progress.  
13:      public bool IsEnumerating { get; private set; }  
14:    
15:      public void Dispose()  
16:      {  
17:          IsEnumerating = false;  
18:          _typedEnumerator?.Dispose();  
19:      }  
20:    
21:      public bool MoveNext()  
22:      {  
23:          IsEnumerating = true;  
24:          return _enumerator.MoveNext();  
25:      }  
26:    
27:      // The rest of the implementation.  
28:  }  
29:  

4) And finally, modify your custom IList<T> implementation to check whether a collection is modified during an enumeration:
1:  class CollectionChangedDuringEnumerationMonitoringList<T> : IList<T>  
2:  {  
3:      NotifyingEnumeratorWrapper _enumerator;  
4:    
5:      public IEnumerator<T> GetEnumerator()  
6:      {  
7:          _enumerator = new NotifyingEnumeratorWrapper(_list.GetEnumerator());  
8:          return _enumerator;  
9:      }  
10:    
11:      public void Add(T item)  
12:      {  
13:          // Add this call for every IList<T> member that modifies a collection.  
14:          OnCollectionChangedDuringEnumeration();  
15:          _list.Add(item);  
16:      }  
17:    
18:      void OnCollectionChangedDuringEnumeration()  
19:      {  
20:          if (_enumerator != null) {  
21:              if (_enumerator.IsEnumerating) {  
22:                  // Setting a breakpoint here will stop a program exactly at the moment  
23:                  // that collection is modified during an enumeration.
24:                  System.Diagnostics.Debugger.Break();  
25:              }  
26:          }  
27:      }  
28:    
29:      // The rest of the implementation.   
30:  }  
31:    

Now you are all set to go. Switch the List type of your collection that has troubles enumerating for this new custom ChangedDuringEnumerationMonitoringList. Then run your application as usual to reproduce the "Collection Was Modified Exception". And the moment your collection is modified during enumeration your application will stop at the breakpoint. Now you can simply observe the stack trace and get straight to fixing your bug!

Having a custom list such as this in your toolbox makes detecting the source of "Collection Was Modified" problem as easy as two Copy/Paste to replace the collection type and one application run to reproduce the bug.

Feel free to take a look at the full implementation of the ChangedDuringEnumerationMonitoringList on my SpaceStrategy GitHub repository.

What are the techniques you are using to find the cause of the "Collection Was Modified Exception"? Please share them here in comments.

No comments:

Post a Comment