Okay guys,
We are wondering if this is a bug in Framework 2.0.40607 and
looking for some clarification on the issue. Take a look at the folowing
code.
public delegate bool BoundryTest(int myVal);
public sealed class Idleness
{
public void Work()
{
int limit = 10;
BoundryTest infinite = delegate(int myVal) { return true; };
BoundryTest finite = delegate(int myVal) { return myVal <
limit; };
limit = 20;
WriteEnumeration(Range(0, finite));
for (; limit < 40; limit++)
;
}
private IEnumerable Range(int lowerBoundry, BoundryTest
boundryDelegate)
{
for (int i = lowerBoundry; boundryDelegate(i); i++)
yield return i;
}
private void WriteEnumeration(IEnumerable list)
{
foreach (object o in list)
Console.WriteLine(o.ToString());
}
}
After reading Juval Lowy's article in the May 2004 issue of MSDN Magazine
entitled "C# 2.0 Create Elegant Code with Anonymous Methods, Iterators, and
Partial Classes" we thought we had a handle how this particular code would
behave. So let me summerize what we understood from Juval's explation of the
implementation of anonymous methods.
The Compiler generates different code based on the anonymous method's use of
values from it's containing method or class. If the anonymous method uses no
values other than it's parameters and local variables (those defined inside
the anonymous method itself), then the compiler generates a static method
that has an identical signature to the delegate's requirement, creates a
variable of the delegate type and assignes the value of the delegate
variable to the introduced static method of the class.
If, on the other hand, the anonymous method uses "outer variables", that is,
those that belong to either the containing method or members of the
containing class, the code that gets generated is a bit different. First
off, the compiler generates an inner class that contains a method whose
signature matches the delegate's signature requirement. Additionally
created are: a "<this>" back pointer to the instance of the containing
class, and public member variables for each outer variable (local to the
containing method) that is used in the anonymous method. In the containing
method where the anonymous method was created, the compiler generates
symantically equivalent code. It creates an instance of the introduced inner
class, copies the necessary values of the methods local variables into the
member variables of the introduced inner class, then creates a variable of
the delegate type, assigns the inctroduced inner class's method (which
matches the delegate' s signature requirement) to the delegate variable and
from then on, simply invokes the delegate.
Okay ... that was a mouthful. But! here's the thing... what we described
above is how we expected the the code to be generated, and therefore had a
particular expectation of how the code would behave. We expected the
application to output the intergers in the range 0 to 9. In actuality the
code behaved contrary to what we expected, instead, intergers in the range 0
to 19 were generated ... This confused us a bit so we took a look at what
was generated and here it is.
public void Work()
{
bool flag1;
c__DisplayClass3 class1 = new c__DisplayClass3();
class1.limit = 10;
if (Idleness.__CachedAnonymousMethodDelegate2 == null)
{
Idleness.__CachedAnonymousMethodDelegate2 = new
BoundryTest(Idleness.Workb__0);
}
BoundryTest test1 = Idleness.__CachedAnonymousMethodDelegate2;
BoundryTest test2 = new BoundryTest(class1.Workb__1);
class1.limit = 20;
this.WriteEnumeration(this.Range(0, test2));
while (class1.limit < 40)
{
c__DisplayClass3 class2 = class1;
class1.limit = (class2.limit + 1);
}
}
What we see from the compiled code (which we got from Reflector 4.0 BTW) is
that our "local variable" limit has been implemented as a member variable of
the introduced inner class.
What we see is that in every use of the "local variable" limit in the
original method body has been replaced by use of the introduced inner
class's member variable limit (class1.limit). Which means any changes to the
value of "local variable" limit is visible to invocations of the delegate.
This behavior is not what is expected because this means that changes to the
"local variable" ought not to be visible to methods other than the one in
which it is declared. Our question is ... Is this the intended behavior or
is this in fact a bug? Is the intention to bind the variables in that way?
Cordell Lawrence [cl*******@teleios-systems.com]