Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't deferred execution cache iterative values?

Take the code below, adapted from this question:

//Borrowed from another question because its a simpler example of what happened to me.
IEnumerable<char> query = "Not what you might expect";
foreach(char vowel in "aeiou")
{
     query = query.Where(c => c != vowel);
}
foreach (char Output in query)
{
    System.Out.WriteLine(Output);
}

This only removes the 'u' from the query char collection. The core issue has something to do with the fact that the c variable in the Where clause isn't evaluated until the second foreach. My question is:

1) Why would the delegate generated by the first foreach not capture each value of c as it is built up? Is there some situation I'm unaware of where that is not the desired behavior?

2) If its not capturing the value of c, how is that value even still in scope in the second foreach when the query is actually run? It would seem to me that if its not storing the values of the variables being passed in, then trying to resolve the statement for the second foreach would fail because the the variable c is clearly out of scope.

I don't understand how it is that 'use the last value we saw on this variable back when it was in scope' was a good design decision for this circumstance, and was hoping someone could shed some light on the subject.

like image 403
GWLlosa Avatar asked Dec 17 '22 07:12

GWLlosa


1 Answers

It is capturing vowel; try:

foreach(char vowel in "aeiou") {
     char tmp = vowel;
     query = query.Where(c => c != tmp);
}

Jon has a list of related posts here.

As for why... foreach is defined (in ECMA 334v4 §15.8.4) as:

A foreach statement of the form `foreach (V v in x) embedded-statement` is then expanded to:

{
  E e = ((C)(x)).GetEnumerator();
  try {
    V v;
    while (e.MoveNext()) {
      v = (V)(T)e.Current;
      embedded-statement
    }
  }
  finally {
    … // Dispose e
  }
}

Note the V is outside the while - this means it is captured once only for the foreach. I gather that the C# team have, at times, doubted the wisdom of this change (it was inside the while in the C# 1.2 spec, which would have avoided the problem completely). Maybe it'll change back eventually, but for now it is being true to the specification.

like image 197
Marc Gravell Avatar answered Dec 19 '22 20:12

Marc Gravell