Peter Duniho said:
[...]
but I'm curious as to what methods are available for working around
the issue Eric mentions. In particular, would it be sufficient to
include the "shortlived" declaration and use in a new code block?
I believe so, yes.
You believe it _would_ be sufficient?
I did - but I didn't have the example in front of me, unfortunately,
and probably wasn't thinking clearly enough. It's the combined scope of
the captured variable and the delegate which is relevant.
Here's Eric's example in a slightly simpler form. I'm using ThreadStart
as a simple parameterless/returnless delegate type - nothing more than
that. Likewise I'm just using string and Stream as random different
types. (Yes, you'd really want to dispose of a stream, etc - irrelevant
to this discussion. Let me know if it bothers you.)
using System;
using System.IO;
using System.Threading;
class Test
{
static void Main()
{
}
static ThreadStart Foo()
{
string cheap = "cheap";
Stream expensive = null;
ThreadStart shortLived = delegate()
{ Console.WriteLine (expensive); };
shortLived();
ThreadStart longLived = delegate()
{ Console.WriteLine (cheap); };
return longLived;
}
}
The problem is that the generated code uses a class <>c__Displayclass2
which has captured *both* "cheap" and "expensive", and longLived has a
reference to an instance of that class.
What we want is for longLived to *only* have an indirect reference to
cheap, not to expensive. It turns out that by adding one level of
scoping, this is achieved:
using System;
using System.IO;
using System.Threading;
class Test
{
static void Main()
{
}
static ThreadStart Foo()
{
string cheap = "cheap";
{
Stream expensive = null;
ThreadStart shortLived = delegate()
{ Console.WriteLine (expensive); };
shortLived();
}
ThreadStart longLived = delegate()
{ Console.WriteLine (cheap); };
return longLived;
}
}
Note that this *isn't* just putting shortLived in its own scope - it's
also changing the scope of expensive. Move the brace to below expensive
and the problem is still there. Apologies if that was the cause of
confusion.
Then perhaps I misunderstand the
lifetime issues. After that statement, I wrote (and you quoted)...
Lifetime is relevant then, and so the queston is...can you affect lifetime
by using a new statement block?
Absolutely.
I thought the answer was "no", but
perhaps I was wrong. A variable that is scoped within a new statement
block, does its lifetime end at the end of that block? Or does it have a
lifetime equal to the containing method? (Capturing aside, of course).
Capturing aside, its lifetime ends at the end of that block. But
capturing is the important part. I suspect I may be using lifetime in
far too woolly a sense at the moment, for which I apologise.
I haven't looked into the exact rules in the spec - and that should
probably be your next port of call, rather than my interpretation of
them, but I suspect the rules are something like:
1) Consider each variable which is captured within a method (even if
they're not captured by the same delegates).
2) For each distinct scope which declares a captured variable, create a
class to represent all captured variables declared in that scope.
"Inner" scopes have references to instances of the classes representing
the "next scope out" (only considering the ones which declare captured
variables).
3) When creating a delegate referencing a captured variable, create an
instance method within the nearest scope to the declaration of the
delegate.
Does that make any kind of sense? (I'm really, really interested in how
best to explain this...)