http://bojordan.com/log/?p=522Anonymous methods in C# can make your code more readable, and they are very convenient. They also have magic powers. For example, when first using them they appear to magically persist local variables! Magic, you say? To show this let’s create a delegate and assign a anonymous method to it, and within the anonymous method we access a local variable:
public delegate void NoArgsDelegate();
private NoArgsDelegate m_myDelegateInstance;
public void ExecuteMeFirst()
{
int i = 42;
m_myDelegateInstance = delegate()
{
Console.WriteLine(">> " + i);
};
}
public void ExecuteMeSecond()
{
if (null != m_myDelegateInstance)
{
m_myDelegateInstance();
}
}
Magically, the value in local variable i is printed to the console! How does this happen? It’s not quite magic; a
closure containing the variable is conveniently created on the heap for the anonymous method referenced by the delegate, giving our formerly stack-based value type a nice long life.
Warning: Prepare for disappointment if you expect to create multiple instances of an anonymous method, each with different version of local variables. Examine the following:
private NoArgsDelegate[] m_myDelegateInstances = new NoArgsDelegate[3];
public void ExecuteMeFirst()
{
for (int i = 0; i < 3; i++)
{
m_myDelegateInstances[i] = delegate()
{
Console.WriteLine(">> " + i);
};
}
}
public void ExecuteMeSecond()
{
foreach (NoArgsDelegate del in m_myDelegateInstances)
{
if (null != del)
{
del();
}
}
}
Contrary to what you might expect, the console does not print 0, 1, and 2. Rather, it prints 2 three times. Why? A single closure is created for an anonymous method in C# code, even if it is referenced multiple times. Therefore, think of an anonymous method (and the closure containing any formerly local variables it references) as static; any modification of the local variable is reflected anywhere it is referenced, even though it’s a value type and it’s modified after the anonymous method is referenced.
If you want different behavior, you must avoid using local variables and pass state into the method yourself. A struct containing the delegate and its data works nicely:
public delegate void OneArgDelegate(int data);
private struct DelegateAndData
{
public OneArgDelegate method;
public int data;
}
private DelegateAndData[] m_myDelegateInstances = new DelegateAndData[3];
public void ExecuteMeFirst()
{
for (int i = 0; i < 3; i++)
{
m_myDelegateInstances[i].method = delegate(int data)
{
Console.WriteLine(">> " + data);
};
m_myDelegateInstances[i].data = i;
}
}
public void ExecuteMeSecond()
{
foreach (DelegateAndData del in m_myDelegateInstances)
{
if (null != del.method)
{
del.method(del.data);
}
}
}