Thursday, October 26, 2006

C# Anonymous Delegates: Your Stack or Mine?

I want to take a moment to call out what I think is an interesting part of my previous post. Anonymous delegates have an interesting capability. To illustrate, here's an example:

using System;
using System.Threading;

class Program
{
static void Main(string[] args)
{
// 'num' lives on the main thread's stack.
int num = 0;
Console.WriteLine("initial num=" + num);

Thread thread = new Thread(
delegate()
{
// Yet this delegate running on a different thread with
// it's own stack has access to 'num' as well.
num = 42;
});

thread.Start(); // Start the worker thread.
thread.Join(); // Wait until the worker thread has finished.

Console.WriteLine("final num=" + num);
}
}



It's pretty simple. This program declares and initializes a variable named num, creates and starts a thread that will set num to a different value, then waits for the thread to to complete. In the program's output we see that num has indeed been set to 42. Here's the view from the command line:


F:\tmp>csc Program.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.


F:\tmp>Program.exe
initial num=0
final num=42


Okay, that's as expected. But wait--num lives on the main thread's stack. How is it that the second thread we've created has access to num? num isn't on the second thread's stack and it would be a pretty scary thing if the second thread had direct access to the first thread's stack. All sorts of mayhem could ensue. Providing access to another thread's stack isn't exactly the sort of thing we want to happen in managed code.


Chicanery?


As it turns out, though it appears that num is a stack variable in the main method it is not. What we're experiencing here is a convenience provided by the compiler. Or rather, a trick. A sleight-of-hand. An illusion. num doesn't actually live on the stack.


In fact, the compiler has made num a field in a compiler generated class so that it can be made available on the heap to the delegate. Here's the compiled code, courtesy of .NET Reflector:


 


[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
// Methods
public <>c__DisplayClass1() { }
public void <main>b__0()
{
this.num = 0x2a;
}
      // Fields
public int num;
}


The compiler has given our anonymous delegate method a name: it's <main>b__0() on this compiler generated class. As a member of this class it obviously has access to num. The illusion is completed by fitting out the Main method so that it has an instance of <>c__DisplayClass1 to use:


 


private static void Main(string[] args)
{
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.num = 0;
Console.WriteLine("initial num=" + class1.num);

Thread thread1 = new Thread(new ThreadStart(class1.
b__0));
thread1.Start();
thread1.Join();

Console.WriteLine("final num=" + class1.num);
}


So now our Main method has an instance of an object on the heap and can set num to zero since num is a public field on the compiler generated class.


And so, both threads are accessing a variable--not on the stack, but on the heap and accessible to both threads. This helpful behind-the-scenes work by the C# compiler allows us to keep the simplicity of the original code in this example; otherwise, we'd need to implement something like what the compiler has done for us. 


 


Technorati tags: , ,

[Edit: Fix up formatting.]

1 comment:

Randolpho said...

I just stumbled on your blog looking for more info on this very issue, and I just wanted to say thanks.

Let me just add this to my feed reader, here.... there.

I look forward to more!