3

An interesting problem with IEnumerable and Linq.

static void Main(string[] args)
{
    var subject = "#Accountancy #premier #Agile #Apache #automation #Automation #banking #Banking #bankIngs #AutoMation";
    var hashtags = subject.Split('#').Select(hashtag => hashtag.Trim().ToUpper()).Distinct();

    var plurals = hashtags.Where((hashtag) =>
    {
        return hashtags.Contains($"{hashtag.ToUpper()}S");
    }).Select(h => $"{h.ToUpper()}S");      //.ToList(); (1) - will not break

    //filter hashtags
    hashtags = hashtags.Except(plurals);    //.ToList(); (2) - will not break

    //if iterate, would break with:
    //System.StackOverflowException was unhandled Message: An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
    foreach (var hashtag in hashtags)
    {
        Console.WriteLine(hashtag);
    }

    Console.Read();
}

Curious on how would one explain why overflow exception occurs?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Alex M
  • 2,410
  • 1
  • 24
  • 37
  • Use a new variable at the filter to avoid changing `hashtags` before fully iteration over it. – wimh Apr 07 '16 at 13:33
  • 1
    Read about **Linq and deferred execution** and combine this readings with the other two answers. Then you'll see why your code breaks. Deferred execution can lead to some pitfalls. – derpirscher Apr 07 '16 at 13:44

2 Answers2

4

Walk through it step by step.

  1. Plurals is every word in hashtags which also has the same word ending in s
  2. Hashtags is every word except plurals.

To execute 2, you must execute 1. However, hashtags is continually changing, so plurals is trying to execute not on the original collection, but on the result of 2 (which again, relies on the result of 1).

Your query will attempt to be:

hashtags = hashtags.Except(plurals);

Replacing plurals

hashtags = hashtags.Except(
            hashtags.Where(hashtag => { return hashtags.Contains($"{hashtag.ToUpper()}S"); })
                    .Select(h => $"{h.ToUpper()}S")
           );

But hashtags is hashtags.Except(plurals);

hashtags.Except(
            hashtags.Except(plurals).Where(hashtag => { return hashtags.Contains($"{hashtag.ToUpper()}S"); })
                    .Select(h => $"{h.ToUpper()}S")
           );

And then we need to replace plurals again.. and so on.

Your fixes (adding .ToList()) is the logical way to fix it.

Rob
  • 26,989
  • 16
  • 82
  • 98
3

You are re-assigning hashtags (which has not been evaluated) to another evaluation which is resulting in an infinite loop. If you put the second evaluation in another variable, it will work:

var hashtags2 = hashtags.Except(plurals);

foreach (var hashtag in hashtags2)
{
    Console.WriteLine(hashtag);
}
Josh Knack
  • 405
  • 2
  • 8
  • 1
    I don't think this is the complete answer - `hashtags = hashtags.Except(hashtags.Where(h => h.StartsWith("B")));` doesn't throw a stack overflow exception. – stuartd Apr 07 '16 at 13:43