A Curious Halfwit HashSet Bug
On Friday I came into work and was greeted with an unhandled exception dialog from Halfwit. I clicked the "Debug" button to drop into Visual Studio, and saw the following stack trace:
{"Collection was modified; enumeration operation may not execute."}
at System.Collections.Generic.HashSet`1.Enumerator.MoveNext()
at System.Linq.Enumerable.<TakeIterator>d__3a`1.MoveNext()
at System.Collections.Generic.HashSet`1.ExceptWith(IEnumerable`1 other)
at Halfwit2.ViewModels.MainViewModel.AddUserNames(IEnumerable`1 userNames)
It didn't give me line numbers, but that's ok, because the AddUserNames
method is really simple:
internal void AddUserNames(IEnumerable<String> userNames)
{
_userNames.UnionWith(userNames);
if (_userNames.Count > 5000)
{
_userNames.ExceptWith(
_userNames.Take(_userNames.Count - 5000));
}
}
So my _userNames
field, which is a HashSet<String>
, has a whole bunch of user names added to it. If it winds up being bigger than 5000 names, I remove the first couple to bring the set size back down to 5000.
Can you see the bug?
The problem is that the parameter I'm passing in, _userNames.Take(...)
, is a "live" IEnumerable. I imagine that ExceptWith
is doing something like this:
foreach (var item in list)
{
if (this.Contains(item)) this.Remove(item);
}
However, the act of removing the item from the set will have modified the "list" that I've passed in (since I'm passing in the same set that I'm modifying)!
The fix is really simple:
_userNames.ExceptWith(
_userNames.Take(_userNames.Count - 5000).ToList());
By adding a .ToList()
call to the end of the Take
, I'm telling to to convert the results into a separate list, so the iteration of the list inside the ExceptWith
call will be independent of what happens to the set.
This is one of those little "gotchas" you can hit when playing with Linq extension methods. Always be aware that you're requerying the same list whenever you use a method like Take
or Skip
!
Trackbacks
- A Curious Halfwit HashSet Bug « Mas-Tool's Favorites | http://mas-tool.com/?p=2761
- All About Computer - Windows Client Developer Roundup 070 for 5/23/2011 | http://ispey.com/computer/computer/windows-client-developer-roundup-070-for-5232011/
- Linksammlung 12/6/2011 | Silverlight, WPF & .NET | http://www.ebnerj.at/blog/?p=1171
No new comments are allowed on this post.
Comments
Jeanine
halfwit indeed -- I've got the same problem in a Silverlight ListBox with an observablecollection source.
I have been trying to fix it for two days now. So effing glad I found this.
Thanx
Oh and you want serious halfwit? try SL sometime. Everything is halfwitted.
Jeanine
and, of course, because everything is like clawing your own eyes out with Silverlight, this doesn't work. Not only that, I had the fun time creating two different calls to the db, both loading completely different objects, different types as well, but somehow, the first object that loaded of that type, somehow, inexplicably, loaded the second object as well, even tho there is NO link anywhere in the code except I loaded them with the same db call...calling the method twice, not once.
So hey, let's just all have magical objects that are somehow coupled because they are of the same object type even if one is a list and the other a obscoll. What the hell, programmers don't need to have separate lists for anything.
Can I just kill myself now and call it a day???
I created a list( of blah) then called blahs.tolist as suggested in multiple sites now that I know what to look for...didn't work. I get the same error because inexplicably, for no reason I can figure out, it thinks one object derived from the other is the same object.
Matt Hamilton
That sounds more like an Entity Framework thing than the problem I describe in this post, Jeanine. Entities with the same primary key are "reused" by EF if you ask the db for them more than once. My suggestion would be to post on Stack Overflow with some code!