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!

halfwit c# .net
Posted by: Matt Hamilton
Last revised: 03 Nov, 2024 01:13 PM History

Trackbacks

Comments

Jeanine
Jeanine
01 Jun, 2012 03:24 PM

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
Jeanine
01 Jun, 2012 04:04 PM

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.

01 Jun, 2012 11:19 PM

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!

No new comments are allowed on this post.