Anoynmous methods and variable life time extension - bug orintendend?

  • Thread starter Martin Carpella
  • Start date
M

Martin Carpella

Hello everyone,

I experience some strange behaviour of anoynmous delegates which refer
to variables outside the scope of the delegate.

Please have a look at the following code and output at the end of this
post and tell me, if the observed behaviour is intentionally or if it is
a bug.

The problem ist the output in the first foreach loop. It seems, that
every created delegate is executed with the _last_ value of val instead
of the value of val when the delgate is created.

As you can see in the output below, in the second foreach where the
delegate is created in its own method, the behaviour is as I expected it
to be.

Did I misunderstand the following part of the documentation?

| Unlike local variables, the lifetime of the outer variable extends until
| the delegates that reference the anonymous methods are eligible for
| garbage collection. A reference to n is captured at the time the
| delegate is created.

Does this really mean, that a reference to the variable is captured and
not a reference to the value? Why then does it work when putting the
delegate creation in its own method?

Please, could anyone comment on this?

Best regards,
Martin Carpella




class Program {
static void Main(string[] args) {
object semaphore = new object();
lock (semaphore) {
string[] values = new string[] { "a", "b", "c" };

foreach (string val in values) {
ThreadPool.QueueUserWorkItem(delegate(object state)
{
lock (semaphore) {
Console.Out.WriteLine("Pool (1): " + val);
} // lock
});
Console.Out.WriteLine("Main (1): " + val);
} // foreach

foreach (string val in values) {
QueueItem(semaphore, val);
Console.Out.WriteLine("Main (2): " + val);
} // foreach
} // foreach

}

private static void QueueItem(object semaphore, string val) {
ThreadPool.QueueUserWorkItem(delegate(object state) {
lock (semaphore) {
Console.Out.WriteLine("Pool (2): " + val);
} // lock
});
}

}

The strange thing is the output:
Main (1): a
Main (1): b
Main (1): c
Main (2): a
Main (2): b
Main (2): c
Pool (1): c
Pool (1): c
Pool (1): c
Pool (2): a
Pool (2): b
Pool (2): c
 
M

Mehdi

I experience some strange behaviour of anoynmous delegates which refer
to variables outside the scope of the delegate.

Please have a look at the following code and output at the end of this
post and tell me, if the observed behaviour is intentionally or if it is
a bug.

The problem ist the output in the first foreach loop. It seems, that
every created delegate is executed with the _last_ value of val instead
of the value of val when the delgate is created.

See Jon Skeet explanation about Captured Variables here:
<http://www.yoda.arachsys.com/csharp/csharp2/delegates.html>
 
J

Jon Skeet [C# MVP]

Joanna Carter said:
| See Jon Skeet explanation about Captured Variables here:
| <http://www.yoda.arachsys.com/csharp/csharp2/delegates.html>

Fascinating stuff!! I *think* I understand it but, can someone give me a
real world but simple example of a useful use for this "feature" :)

I haven't used C# 2.0 much in anger yet, but Groovy has a very similar
notion with closures. Because the closures don't need a separate
definition (unlike delegates) you can use them much more widely without
introducing a load of extra types. (This comes at the cost of compile-
time safety, admittedly.)

This makes it really easy to do a lot of things. For instance, here's a
sample Groovy script to print out a file and its line numbers:

line=0
new File ("test.groovy").eachLine {
line++
println "$line: $it"
}


The "using" statement in C# is effectively a very specific type of
closure - Groovy applies the concept very widely. C#/.NET 2.0 allows
similar things to let you do something which each element in a list
etc, but it's not quite as straight-forward as in Groovy.
 
B

Barry Kelly

Joanna Carter said:
"Mehdi" <[email protected]> a écrit dans le message de [email protected]...

| See Jon Skeet explanation about Captured Variables here:
| <http://www.yoda.arachsys.com/csharp/csharp2/delegates.html>

Fascinating stuff!! I *think* I understand it but, can someone give me a
real world but simple example of a useful use for this "feature" :)

I use closures all the time for parameterising classes where creating a
new class is more trouble than it's worth.

For example, I have a Resource class which implements IDisposable. Its
constructor takes in a delegate which takes no arguments. This delegate
gets called when the class gets disposed. Here's a quick 2-minute
rewrite with no error checking etc.:

---8<---
delegate void Method();

class Resource : IDisposable
{
private Method _onDispose;

public Resource(Method onDispose)
{
_onDispose = onDispose;
}

public void Dispose()
{
_onDispose();
}

public static Resource ReaderLock(ReaderWriterLock rw,
TimeSpan timeout)
{
rw.AcquireReaderLock(timeout);
return new Resource(delegate { rw.ReleaseReaderLock(); });
}
}
--->8---

I can then write methods anywhere in my framework that can return an
object to be used in a using() block wherever these acquire-release
semantics are used.

Another usage is that it is sometimes actually faster to pass a delegate
than it is to code "outside" the class:

---8<---
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

class App
{
delegate void Method();

static void Benchmark(string label, int iterations, Method block)
{
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; ++i)
block();
Console.WriteLine("{0}: {1:f3}", label, watch.ElapsedTicks /
(double) Stopwatch.Frequency);
}

static void Main()
{
List<int> list = new List<int>();
Random r = new Random();

int iterations = 2000;

for (int i = 0; i < 300000; ++i)
list.Add(r.Next());

Benchmark("Using for-loop: ", iterations, delegate
{
int total = 0;
for (int i = 0; i < list.Count; ++i)
total += list;
});

Benchmark("Using foreach: ", iterations, delegate
{
int total = 0;
foreach (int x in list)
total += x;
});

Benchmark("Using ForEach: ", iterations, delegate
{
int total = 0;
list.ForEach(delegate(int x) { total += x; });
});
}
}
--->8---

This produces the following output on my machine (Athlon 3500+):

Using for-loop: : 2.902
Using foreach: : 5.044
Using ForEach: : 2.854

-- Barry
 
B

Barry Kelly

Closures are my favourite feature of C# 2.0. I can live without
generics, but closures have been something I've been looking for in
mainstream languages for far too long. Anonymous delegates aren't
identical to closures because they track the object variables
themselves, rather than the object values at construction time, but
they're a good simulacrum in most practical situations. If you really
need the copy semantics, it's trivial to put a {} block outside the
delegate constructor, and explicitly copy all the variables to capture
to locals declared inside this block.

I think I should add a commentary, just to make a few observations.
Benchmark("Using for-loop: ", iterations, delegate
{
int total = 0;
for (int i = 0; i < list.Count; ++i)
total += list;


Here, list is a reference to the captured list, so it is an instance
field in the automagically created class that holds the closure, while
total is a local. That should mean that each time around the loop, the
'list' field of this instance is being loaded twice. So, two field
accesses and a local access.
});

Benchmark("Using foreach: ", iterations, delegate
{
int total = 0;
foreach (int x in list)
total += x;

This will get a nice valuetype IEnumerator<int> implementation from the
list up front, so each time around the loop two methods are called
(MoveNext() and get_Current()) which can be inlined (because valuetypes
can't be subclassed), along with a local access. It turns out to take
70% longer than the next slowest though - interesting!
});

Benchmark("Using ForEach: ", iterations, delegate
{
int total = 0;
list.ForEach(delegate(int x) { total += x; });

In the previous two loops, total is a local, while in this one it's a
captured variable and thus is an instance field on a generated class for
the closure. That means it's actually got a speed-hit built-in that the
others don't have - yet it still turns out to be faster than the other
two.

One can infer from that that if you're doing something where you can
});
}
}
--->8---

-- Barry
 
W

William Stacey [MVP]

Cool. This is how you might do it in c#:



List<string> l = new List<string>(File.ReadAllLines(@"c:\myfile.txt"));

int line = 0;

l.ForEach(delegate(string s) {

line++;

Console.WriteLine("{0}: {1}", line, s);

});


--
William Stacey [MVP]

| > | See Jon Skeet explanation about Captured Variables here:
| > | <http://www.yoda.arachsys.com/csharp/csharp2/delegates.html>
| >
| > Fascinating stuff!! I *think* I understand it but, can someone give me a
| > real world but simple example of a useful use for this "feature" :)
|
| I haven't used C# 2.0 much in anger yet, but Groovy has a very similar
| notion with closures. Because the closures don't need a separate
| definition (unlike delegates) you can use them much more widely without
| introducing a load of extra types. (This comes at the cost of compile-
| time safety, admittedly.)
|
| This makes it really easy to do a lot of things. For instance, here's a
| sample Groovy script to print out a file and its line numbers:
|
| line=0
| new File ("test.groovy").eachLine {
| line++
| println "$line: $it"
| }
|
|
| The "using" statement in C# is effectively a very specific type of
| closure - Groovy applies the concept very widely. C#/.NET 2.0 allows
| similar things to let you do something which each element in a list
| etc, but it's not quite as straight-forward as in Groovy.
|
| --
| Jon Skeet - <[email protected]>
| http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
| If replying to the group, please do not mail me too
 
L

Lucian Wischik

Joanna Carter said:
Fascinating stuff!! I *think* I understand it but, can someone give me a
real world but simple example of a useful use for this "feature" :)

In addition to the other comments, I'd just add that delegates have
been around in other languages for a while -- like Pascal and
Modula-3. They're also a natural part of the lambda-calculus, on which
much of computer science theory is based.

If you allow the ability to define nested procedures -- i.e. to define
procedures within other procedures -- then the variable lifetime stuff
we see in C# is inevitable.
 
J

Joanna Carter [TeamB]

"Lucian Wischik" <[email protected]> a écrit dans le message de (e-mail address removed)...

| In addition to the other comments, I'd just add that delegates have
| been around in other languages for a while -- like Pascal and
| Modula-3. They're also a natural part of the lambda-calculus, on which
| much of computer science theory is based.
|
| If you allow the ability to define nested procedures -- i.e. to define
| procedures within other procedures -- then the variable lifetime stuff
| we see in C# is inevitable.

Thenks to you and others for replies about the closures or delegates but,
,coming from a Delphi background we have been used to passing procedures and
nested procedures.

What I am really interested in finding out is this lifetime stuff and a
real-world use for the way the variables change/persist inside the
delegates. It looks both cool and wierd at the same time. Lots of things
like this can be solutions looking for a problem, so my real question is :
what is the problem that it can solve ?

:))

Joanna
 
L

Lucian Wischik

Joanna Carter said:
It looks both cool and wierd at the same time. Lots of things
like this can be solutions looking for a problem, so my real question is :
what is the problem that it can solve ?

My take: it can't solve anything. Everything you could do with
anonymous delegates, you could instead do by creating a top-level
procedure and passing around a load of extra arguments to it. Or by
creating a class with a single "Invoke()" method on it. Anonymous
delegates are simply an easier way to write some of these things.

Most of the time they don't feel weird. If ever they do start to feel
weird, then your code probably won't be maintainable to other people
and you should stop!
 
J

Jon Skeet [C# MVP]

William Stacey said:
Cool. This is how you might do it in c#:

List<string> l = new List<string>(File.ReadAllLines(@"c:\myfile.txt"));

int line = 0;

l.ForEach(delegate(string s) {
line++;
Console.WriteLine("{0}: {1}", line, s);
});

Except that that requires the whole file to be read to start with,
unfortunately :( It's also a bit more work even just due to the
delegate (string s) bit. It's a lot nicer than alternatives in both C#
and "normal" Java (anonymous inner classes - blech) but it doesn't
encourage this style of programming to become widespread. In Groovy
it's rife :)
 
J

Jon Skeet [C# MVP]

Lucian Wischik said:
My take: it can't solve anything. Everything you could do with
anonymous delegates, you could instead do by creating a top-level
procedure and passing around a load of extra arguments to it. Or by
creating a class with a single "Invoke()" method on it. Anonymous
delegates are simply an easier way to write some of these things.

Most of the time they don't feel weird. If ever they do start to feel
weird, then your code probably won't be maintainable to other people
and you should stop!

I think they feel *particularly* weird when you have multiple closures
capturing different "versions" of a particular variable. It gets even
weirder when those closures may capture all the same version of a
different variable.
 
B

Barry Kelly

Jon Skeet said:
[...] It's also a bit more work even just due to the
delegate (string s) bit.

Iterating through a List<T> using a delegate passed to ForEach is
actually faster.

-- Barry
 
B

Barry Kelly

Lucian Wischik said:
My take: it can't solve anything. Everything you could do with
anonymous delegates, you could instead do by creating a top-level
procedure and passing around a load of extra arguments to it.
Or by creating a class with a single "Invoke()" method on it.

Of course, delegates are simply classes with a single Invoke() method.
Anonymous
delegates are simply an easier way to write some of these things.

They provide an extra abstraction tool - a way to easily pass arbitrary
code to other methods as if it were data.

For example, anonymous delegates can provide a way to make the Strategy
pattern easier to implement for cases where one might not consider it
because it might be too tedious. The Benchmark "algorithm" in my example
above is an example of a simple Strategy approach. The Benchmark
algorithm of timing an iterated loop over a block of code is
encapsulated in one place, and the code to execute is passed in. The
advantage of this over other techniques such as creating a separate
class is that it's more simple (when you understand the abstraction).

Just like object-oriented decomposition of a problem domain is a tool
that can make programs simpler when you understand how it works, passing
code blocks around can also make programs simpler.

-- Barry
 
W

William Stacey [MVP]

| Except that that requires the whole file to be read to start with,
| unfortunately

True. But as the example ends up reading the whole file anyway, not sure it
matters all that much unless the file is large.
Not sure if the groovy "eachline" internally reads the whole thing into a
list first or not. I suppose File could implement an iterator on text files
to do the same kind of thing. Something like:

foreach(string s in File.ForEach("c:\my.txt"))
{
Console.WriteLine(s);
}
 
J

Jon Skeet [C# MVP]

William Stacey said:
| Except that that requires the whole file to be read to start with,
| unfortunately

True. But as the example ends up reading the whole file anyway, not sure it
matters all that much unless the file is large.
Not sure if the groovy "eachline" internally reads the whole thing into a
list first or not.

I would be absolutely gobsmacked if it did. That would defeat most of
the point.
I suppose File could implement an iterator on text files
to do the same kind of thing. Something like:

foreach(string s in File.ForEach("c:\my.txt"))
{
Console.WriteLine(s);
}

Yes, that would indeed achieve the same kind of thing for this case.
 
J

Jon Skeet [C# MVP]

Barry Kelly said:
Jon Skeet said:
[...] It's also a bit more work even just due to the
delegate (string s) bit.

Iterating through a List<T> using a delegate passed to ForEach is
actually faster.

I meant work in terms of code. It doesn't look as neat.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top