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: 19 May, 2012 02:41 AM History

Trackbacks

Comments

No comments yet. Be the first!

Your Comments

Used for your gravatar. Not required. Will not be public.
Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview