very strange effect with anonymous delegates (.net 2.0)

  • Thread starter Thread starter cody
  • Start date Start date
C

cody

I have a very funny/strange effect here.

if I let the delegate do "return
prop.GetGetMethod().Invoke(info.AudioHeader, null);"
then I get wrong results, that is, a wrong method is called and I have no
clue why.
But if I store the MethodInfo in a local variable I works as expected.

I do not understand why this is so, shouldn't both ways be semantically
equal?

foreach (PropertyInfo prop in typeof(Au.AuHeader).GetProperties())
{
ColumnInfo col = ColumnInfoFromProperty(prop);

// very strange effect: If I do not store MethodInfo here in a local
variable,
// but instead call prop.GetGetMethod() directly from the delegate I get
wrong results
MethodInfo mi = prop.GetGetMethod();

col.SortValue = delegate(FileInformation info) { return
mi.Invoke(info.AudioHeader, null); };

cols.Add(col);
}
 
cody said:
I have a very funny/strange effect here.

if I let the delegate do "return
prop.GetGetMethod().Invoke(info.AudioHeader, null);"
then I get wrong results, that is, a wrong method is called and I have no
clue why.
But if I store the MethodInfo in a local variable I works as expected.

I do not understand why this is so, shouldn't both ways be semantically
equal?

foreach (PropertyInfo prop in typeof(Au.AuHeader).GetProperties())
{
ColumnInfo col = ColumnInfoFromProperty(prop);

// very strange effect: If I do not store MethodInfo here in a local
variable,
// but instead call prop.GetGetMethod() directly from the delegate I
get wrong results
MethodInfo mi = prop.GetGetMethod();

col.SortValue = delegate(FileInformation info) { return
mi.Invoke(info.AudioHeader, null); };

cols.Add(col);
}

Ahh the wonders of anonymous delegates and closures.

Have you had a look at what the IL looks like for a method with an anonymous
delegate? You get a generated class and all data that is accessed from the
enclosing scope become members of that class.

In the first case prop becomes a member of the class in the second mi does.
Do you see the problem? prop changes on each iteration, therefore you end up
creating a delegate referring to the place where the iterator got to when
the delegete fired.

In the second, each mi is local to each iteration so the delegate gets the
correct property

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk
 
I have a very funny/strange effect here.
Ahh the wonders of anonymous delegates and closures.

Have you had a look at what the IL looks like for a method with an
anonymous delegate? You get a generated class and all data that is
accessed from the enclosing scope become members of that class.

In the first case prop becomes a member of the class in the second mi
does. Do you see the problem? prop changes on each iteration, therefore
you end up creating a delegate referring to the place where the iterator
got to when the delegete fired.

In the second, each mi is local to each iteration so the delegate gets the
correct property

Sorry, maybe Iam stupid, but I still do not see the difference.
Both mi and prop are local variables which are 'lifted' to a field, right
when
the delegate is created. So there shouldn't be any difference, should it?
 
cody said:
Sorry, maybe Iam stupid, but I still do not see the difference.
Both mi and prop are local variables which are 'lifted' to a field, right
when
the delegate is created. So there shouldn't be any difference, should it?

But prop is changing every time around the loop, mi is local to the loop and
so is fixed for each iteration.

If you look at the generated code (e.g in reflector) you will see that in
the case of using the iterator, the generated class is instantiated once
outside of the loop as that is the scope of the iterator.

In the case of the local variable, the generated class is instantiated
inside the loop as that is the scope of mi.#

So for the iterator the single integer value ends up as the last iterated
one, for the local variable its each value is captured in a separate
instance.

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk
 
Ok I had a look in reflector and I now see the problem. Since in generated
code there is no local variable "PropertyInfo prop" but instead just a call
to "enumerable.Current" so there is nothing to 'lift'.

This maybe right from the view of the ILCode but it is toally wrong from the
view of the sourcecode since "PropertyInfo prop" is a readonly variable from
the programmer's view.

Iam the opinion that is is a very dangerous pitfall. If MS won't fix that,
they should at least throw a compiler error when a foreach iterator is used
in a anonymous method.

What do you think about that?

"Richard Blewett [DevelopMentor]" <richard at nospam dotnetconsult dot co
dot uk> schrieb im Newsbeitrag news:[email protected]...
 
cody said:
Ok I had a look in reflector and I now see the problem. Since in generated
code there is no local variable "PropertyInfo prop" but instead just a
call to "enumerable.Current" so there is nothing to 'lift'.

This maybe right from the view of the ILCode but it is toally wrong from
the view of the sourcecode since "PropertyInfo prop" is a readonly
variable from the programmer's view.

Iam the opinion that is is a very dangerous pitfall. If MS won't fix that,
they should at least throw a compiler error when a foreach iterator is
used in a anonymous method.

What do you think about that?

Surely its simply following standard scoping rules - its declaring the class
at the outermost scope of the local variables that are used within the anon
delegate. It obviously can't do it within the scope as the outermost one
wouldn't be in scope at that point.

So what you are asking for is special casing of your situation (I'm not
lambasting you for this just stating what you are asking). This is an
equally valid construct:

int i = 0;
int[] ii = int[3] { 1, 2, 3};

foreach ( i in ii )
{
// blah
}

this has basically the same scope as your iterator version - what would you
have the compiler do here?

I can understand your frustration, the closure like feature of anon
delegates can lead to some weird behavior if you don't know whats ahppening
behind the scenes. But asking fofr compiler special casig of your construct
is alot to ask (but it has been done before)

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk
 
Back
Top